Compare commits
34 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5a7e7f6f2c | |
|
|
f4171fe60b | |
|
|
a834762450 | |
|
|
2a81e3f700 | |
|
|
95a68d3be5 | |
|
|
bdf02b7764 | |
|
|
b1e8c310c5 | |
|
|
fe373e2c3f | |
|
|
8a1e19dc11 | |
|
|
a77d5eb257 | |
|
|
f69d47a87e | |
|
|
616d9979e0 | |
|
|
1b8039809d | |
|
|
36c2df79e4 | |
|
|
77719f1bde | |
|
|
102e97cc9a | |
|
|
2aee784f51 | |
|
|
c56a9f7155 | |
|
|
240688a4e2 | |
|
|
713d0fa3b3 | |
|
|
e70179b362 | |
|
|
58377bcfbc | |
|
|
bcee9db3de | |
|
|
2403d96e90 | |
|
|
ece8921cfe | |
|
|
693acdc032 | |
|
|
ae23509c4a | |
|
|
bc16465ae0 | |
|
|
3115243f55 | |
|
|
759f562316 | |
|
|
fdf08687d6 | |
|
|
9708eba6cb | |
|
|
d4e779fc66 | |
|
|
e0ee97bd7f |
|
|
@ -1,5 +1,6 @@
|
|||
ignore:
|
||||
- "Tests/"
|
||||
- "Benchmarks/"
|
||||
|
||||
comment:
|
||||
layout: header, changes, diff
|
||||
layout: header, changes, diff
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ on:
|
|||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
linux-test-build-release:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
|
||||
- name: Upload test artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts-linux-${{ matrix.swift }}-${{ github.run_id }}
|
||||
path: |
|
||||
|
|
|
|||
|
|
@ -9,32 +9,34 @@ on:
|
|||
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
CODECOV_XCODE_VERSION: "15.4" # Xcode version used to generate code coverage
|
||||
CODECOV_XCODE_VERSION: "16.0" # Xcode version used to generate code coverage
|
||||
|
||||
jobs:
|
||||
macos-test-build-release-xcode:
|
||||
runs-on: macOS-latest
|
||||
macos:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
xcode: ["14.3.1", "15.4", "16.0"]
|
||||
|
||||
config:
|
||||
- { os: "macos-14", xcode: "15.4" }
|
||||
- { os: "macos-15", xcode: "16.0" }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Select Xcode ${{ matrix.xcode }}
|
||||
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
- name: Select Xcode ${{ matrix.config.xcode }}
|
||||
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.config.xcode }}.app
|
||||
|
||||
- name: Test
|
||||
run: swift test -c release --parallel --xunit-output .build/xUnit-output.xml --enable-code-coverage
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.config.xcode }}.app/Contents/Developer
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts-${{ matrix.xcode }}-${{ github.run_id }}
|
||||
name: test-artifacts-${{ matrix.config.xcode }}-${{ github.run_id }}
|
||||
path: |
|
||||
.build/*.yaml
|
||||
.build/*.xml
|
||||
|
|
@ -48,13 +50,13 @@ jobs:
|
|||
|
||||
# Only run coverage steps if the CODECOV_TOKEN is available and the matrix.xcode matches CODECOV_XCODE_VERSION
|
||||
- name: Generate coverage report
|
||||
if: env.CODECOV_TOKEN != '' && matrix.xcode == env.CODECOV_XCODE_VERSION
|
||||
if: env.CODECOV_TOKEN != '' && matrix.config.xcode == env.CODECOV_XCODE_VERSION
|
||||
run: xcrun llvm-cov export -format="lcov" .build/**/*PackageTests.xctest/Contents/MacOS/*PackageTests -instr-profile .build/**/codecov/default.profdata > coverage.lcov
|
||||
|
||||
- name: Upload code coverage report
|
||||
if: env.CODECOV_TOKEN != '' && matrix.xcode == env.CODECOV_XCODE_VERSION
|
||||
uses: codecov/codecov-action@v4.6.0
|
||||
if: env.CODECOV_TOKEN != '' && matrix.config.xcode == env.CODECOV_XCODE_VERSION
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
with:
|
||||
token: ${{ env.CODECOV_TOKEN }}
|
||||
file: coverage.lcov
|
||||
files: coverage.lcov
|
||||
fail_ci_if_error: true
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ on:
|
|||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
wasm-build:
|
||||
wasm:
|
||||
runs-on: ubuntu-latest
|
||||
container: swift:6.0.3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
uses: swiftwasm/swiftwasm-action@v5.9
|
||||
with:
|
||||
shell-action: swift build --triple wasm32-unknown-wasi
|
||||
- uses: swiftwasm/setup-swiftwasm@v2
|
||||
- run: swift build --swift-sdk wasm32-unknown-wasi
|
||||
|
|
|
|||
|
|
@ -6,12 +6,20 @@ on:
|
|||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
defaults:
|
||||
run: # Use powershell because bash is not supported: https://github.com/compnerd/gha-setup-swift/issues/18#issuecomment-1705524890
|
||||
shell: pwsh
|
||||
|
||||
jobs:
|
||||
windows-test-build-release:
|
||||
runs-on: windows-latest
|
||||
windows:
|
||||
runs-on: windows-2019 # Windows SDK lower than 10.0.26100 is needed until https://github.com/swiftlang/swift/pull/79751 released!
|
||||
steps:
|
||||
|
||||
- name: Setup VS Dev Environment
|
||||
uses: seanmiddleditch/gha-setup-vsdevenv@v5
|
||||
|
||||
- name: Setup
|
||||
uses: compnerd/gha-setup-swift@v0.2.3
|
||||
uses: compnerd/gha-setup-swift@v0.3.0
|
||||
with:
|
||||
branch: swift-5.10-release
|
||||
tag: 5.10-RELEASE
|
||||
|
|
@ -24,7 +32,7 @@ jobs:
|
|||
|
||||
- name: Upload test artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-artifacts-windows-${{ github.run_id }}
|
||||
path: |
|
||||
|
|
|
|||
|
|
@ -1,32 +1,76 @@
|
|||
# Build and deploy DocC to GitHub pages. Based off of pointfreeco/swift-composable-architecture:
|
||||
# https://github.com/pointfreeco/swift-composable-architecture/blob/main/.github/workflows/documentation.yml
|
||||
name: Documentation
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- Sources/FirebladeECS/**.swift
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- .github/workflows/documentation.yml
|
||||
- Sources/FirebladeECS/**.swift
|
||||
|
||||
concurrency:
|
||||
group: docs-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
documentation:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Generate Swift Doc Documentation
|
||||
uses: SwiftDocOrg/swift-doc@1.0.0-rc.1
|
||||
- name: Select Xcode 16.0
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.0.app
|
||||
|
||||
- name: Checkout Package
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
inputs: "Sources/FirebladeECS"
|
||||
output: "Documentation"
|
||||
- name: Publish to wiki
|
||||
uses: SwiftDocOrg/github-wiki-publish-action@master
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout gh-pages Branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: "Documentation"
|
||||
env:
|
||||
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
ref: gh-pages
|
||||
path: docs
|
||||
|
||||
- name: Build documentation
|
||||
run: >
|
||||
rm -rf docs/.git;
|
||||
rm -rf docs/master;
|
||||
git tag -l --sort=-v:refname | grep -e "\d\+\.\d\+.*" | tail -n +6 | xargs -I {} rm -rf {};
|
||||
|
||||
for tag in $(echo "master"; git tag -l --sort=-v:refname | grep -e "\d\+\.\d\+.*" | head -6);
|
||||
do
|
||||
if [ -d "docs/$tag/data/documentation/firebladeecs" ]
|
||||
then
|
||||
echo "✅ Documentation for "$tag" already exists.";
|
||||
else
|
||||
echo "⏳ Generating documentation for FirebladeECS @ "$tag" release.";
|
||||
rm -rf "docs/$tag";
|
||||
|
||||
git checkout .;
|
||||
git checkout "$tag";
|
||||
|
||||
DOCC_JSON_PRETTYPRINT=YES \
|
||||
swift package \
|
||||
--allow-writing-to-directory docs/"$tag" \
|
||||
generate-documentation \
|
||||
--fallback-bundle-identifier com.github.fireblade-engine.FirebladeECS \
|
||||
--target FirebladeECS \
|
||||
--output-path docs/"$tag" \
|
||||
--transform-for-static-hosting \
|
||||
--hosting-base-path ecs/"$tag" \
|
||||
&& echo "✅ Documentation generated for FirebladeECS @ "$tag" release." \
|
||||
|| echo "⚠️ Documentation skipped for FirebladeECS @ "$tag".";
|
||||
fi;
|
||||
done
|
||||
|
||||
- name: Fix permissions
|
||||
run: 'sudo chown -R $USER docs'
|
||||
|
||||
- name: Publish documentation to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4.7.3
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs
|
||||
single-commit: true
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v4
|
||||
- name: markdown-link-check
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@master
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@1.0.16
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FirebladeECS]
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// Base.swift
|
||||
// FirebladeECSTests
|
||||
//
|
||||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
import FirebladeECS
|
||||
|
||||
class EmptyComponent: Component {}
|
||||
|
||||
class Name: Component {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
class Position: Component {
|
||||
var x: Int
|
||||
var y: Int
|
||||
init(x: Int, y: Int) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
}
|
||||
|
||||
class Velocity: Component {
|
||||
var a: Float
|
||||
init(a: Float) {
|
||||
self.a = a
|
||||
}
|
||||
}
|
||||
|
||||
class Party: Component {
|
||||
var partying: Bool
|
||||
init(partying: Bool) {
|
||||
self.partying = partying
|
||||
}
|
||||
}
|
||||
|
||||
class Color: Component {
|
||||
var r: UInt8 = 0
|
||||
var g: UInt8 = 0
|
||||
var b: UInt8 = 0
|
||||
}
|
||||
|
||||
class ExampleSystem {
|
||||
private let family: Family2<Position, Velocity>
|
||||
|
||||
init(nexus: Nexus) {
|
||||
family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self)
|
||||
}
|
||||
|
||||
func update(deltaT _: Double) {
|
||||
for (position, velocity) in family {
|
||||
position.x *= 2
|
||||
velocity.a *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SingleGameState: SingleComponent {
|
||||
var shouldQuit: Bool = false
|
||||
var playerHealth: Int = 67
|
||||
}
|
||||
|
||||
func setUpNexus() -> Nexus {
|
||||
let numEntities = 10000
|
||||
let nexus = Nexus()
|
||||
|
||||
for i in 0 ..< numEntities {
|
||||
nexus.createEntity().assign(Position(x: 1 + i, y: 2 + i),
|
||||
Name(name: "myName\(i)"),
|
||||
Velocity(a: 3.14),
|
||||
EmptyComponent(),
|
||||
Color())
|
||||
}
|
||||
|
||||
precondition(nexus.numEntities == numEntities)
|
||||
// precondition(nexus.numFamilies == 1)
|
||||
precondition(nexus.numComponents == numEntities * 5)
|
||||
|
||||
return nexus
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
// swiftformat:disable preferForLoop
|
||||
import Benchmark
|
||||
import FirebladeECS
|
||||
|
||||
// derived from FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift in the parent project
|
||||
|
||||
let benchmarks = {
|
||||
Benchmark("TraitMatching") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let a = nexus.createEntity()
|
||||
a.assign(Position(x: 1, y: 2))
|
||||
a.assign(Name(name: "myName"))
|
||||
a.assign(Velocity(a: 3.14))
|
||||
a.assign(EmptyComponent())
|
||||
|
||||
let isMatch = nexus.family(requiresAll: Position.self, Velocity.self,
|
||||
excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
isMatch.canBecomeMember(a)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntities") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entities
|
||||
.forEach { (entity: Entity) in
|
||||
_ = entity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyOneComponent") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position) in
|
||||
_ = position
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityOneComponent") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position) in
|
||||
_ = entity
|
||||
_ = position
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyTwoComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Benchmark("TypedFamilyEntityTwoComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyThreeComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Benchmark("TypedFamilyEntityThreeComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyFourComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name, color: Color) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityFourComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name, color: Color) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyFiveComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name, color: Color, empty: EmptyComponent) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
_ = empty
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityFiveComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name, color: Color, empty: EmptyComponent) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
_ = empty
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// swift-tools-version: 5.8
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ECSBenchmarks",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v13)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../"),
|
||||
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.29.2"))
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "ECSBenchmark",
|
||||
dependencies: [
|
||||
.product(name: "FirebladeECS", package: "ecs"),
|
||||
.product(name: "Benchmark", package: "package-benchmark")
|
||||
],
|
||||
path: "Benchmarks/ECSBenchmark",
|
||||
plugins: [
|
||||
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Benchmarks for FirebladeECS
|
||||
|
||||
Originally seeded by replicating performance tests into a new form leveraging [package-benchmark](https://swiftpackageindex.com/ordo-one/package-benchmark/) [Documentation](https://swiftpackageindex.com/ordo-one/package-benchmark/main/documentation/benchmark).
|
||||
|
||||
To run all the available benchmarks:
|
||||
|
||||
swift package benchmark --format markdown
|
||||
|
||||
For more help on the package-benchmark SwiftPM plugin:
|
||||
|
||||
swift package benchmark help
|
||||
|
||||
Creating a local baseline:
|
||||
|
||||
swift package --allow-writing-to-package-directory benchmark baseline update dev
|
||||
swift package benchmark baseline list
|
||||
|
||||
Comparing to a the baseline 'alpha'
|
||||
|
||||
swift package benchmark baseline compare dev
|
||||
|
||||
For more details on creating and comparing baselines, read [Creating and Comparing Benchmark Baselines](https://swiftpackageindex.com/ordo-one/package-benchmark/main/documentation/benchmark/creatingandcomparingbaselines).
|
||||
34
Makefile
34
Makefile
|
|
@ -59,3 +59,37 @@ clean: clean-sourcery
|
|||
.PHONY: clean-sourcery
|
||||
clean-sourcery:
|
||||
rm -rdf ${HOME}/Library/Caches/Sourcery
|
||||
|
||||
# Preview DocC documentation
|
||||
.PHONY: preview-docs
|
||||
preview-docs:
|
||||
swift package --disable-sandbox preview-documentation --target FirebladeECS
|
||||
|
||||
# Preview DocC documentation with analysis/warnings and overview of coverage
|
||||
.PHONY: preview-analysis-docs
|
||||
preview-analysis-docs:
|
||||
swift package --disable-sandbox preview-documentation --target FirebladeECS --analyze --experimental-documentation-coverage --level brief
|
||||
|
||||
# Generates a plain DocC archive in the .build directory
|
||||
.PHONY: generate-docs
|
||||
generate-docs:
|
||||
DOCC_JSON_PRETTYPRINT=YES \
|
||||
swift package \
|
||||
generate-documentation \
|
||||
--fallback-bundle-identifier com.github.fireblade-engine.FirebladeECS \
|
||||
--target FirebladeECS \
|
||||
|
||||
# Generates documentation pages suitable to push/host on github pages (or another static site)
|
||||
# Expected location, if set up, would be:
|
||||
# https://fireblade-engine.github.io/FirebladeECS/documentation/FirebladeECS/
|
||||
.PHONY: generate-docs-githubpages
|
||||
generate-docs-githubpages:
|
||||
DOCC_JSON_PRETTYPRINT=YES \
|
||||
swift package \
|
||||
--allow-writing-to-directory ./docs \
|
||||
generate-documentation \
|
||||
--fallback-bundle-identifier com.github.fireblade-engine.FirebladeECS \
|
||||
--target FirebladeECS \
|
||||
--output-path ./docs \
|
||||
--transform-for-static-hosting \
|
||||
--hosting-base-path 'FirebladeECS'
|
||||
2
Mintfile
2
Mintfile
|
|
@ -1,3 +1,3 @@
|
|||
realm/SwiftLint@0.57.0
|
||||
nicklockwood/SwiftFormat@0.54.6
|
||||
nicklockwood/SwiftFormat@0.55.0
|
||||
krzysztofzablocki/Sourcery@2.2.5
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-docc-plugin",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-docc-plugin",
|
||||
"state" : {
|
||||
"revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
|
||||
"version" : "1.4.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-docc-symbolkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swiftlang/swift-docc-symbolkit",
|
||||
"state" : {
|
||||
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
|
||||
"version" : "1.0.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
|
|
@ -7,6 +7,9 @@ let package = Package(
|
|||
.library(name: "FirebladeECS",
|
||||
targets: ["FirebladeECS"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.3")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "FirebladeECS",
|
||||
exclude: ["Stencils/Family.stencil"]),
|
||||
|
|
|
|||
|
|
@ -233,8 +233,9 @@ See the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo) t
|
|||
|
||||
## 📖 Documentation
|
||||
|
||||
Consult the [wiki](https://github.com/fireblade-engine/ecs/wiki) for in-depth [documentation](https://github.com/fireblade-engine/ecs/wiki).
|
||||
Consult the [online documentation](https://swiftpackageindex.com/fireblade-engine/ecs/documentation/FirebladeECS), or preview it locally:
|
||||
|
||||
- `make preview-docs`
|
||||
|
||||
## 💁 How to contribute
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
# ``FirebladeECS``
|
||||
|
||||
Seamlessly, consistently, and asynchronously replicate data.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component System](https://en.wikipedia.org/wiki/Entity_component_system) implementation in Swift.
|
||||
An ECS comprises entities composed from components of data, with systems which operate on the components.
|
||||
|
||||
Fireblade ECS is available for all platforms that support [Swift 5.8](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager).
|
||||
It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine).
|
||||
|
||||
For a more detailed example of FirebladeECS in action, see the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo).
|
||||
|
||||
## Topics
|
||||
|
||||
### Essentials
|
||||
|
||||
- <doc:GettingStartedWithFirebladeECS>
|
||||
- ``Nexus``
|
||||
- ``NexusEvent``
|
||||
- ``NexusEventDelegate``
|
||||
|
||||
### Entities
|
||||
|
||||
- ``Entity``
|
||||
- ``EntityState``
|
||||
- ``EntityStateMachine``
|
||||
- ``EntityCreated``
|
||||
- ``EntityDestroyed``
|
||||
- ``EntityComponentHash``
|
||||
- ``EntityIdentifier``
|
||||
- ``EntityIdentifierGenerator``
|
||||
- ``DefaultEntityIdGenerator``
|
||||
- ``LinearIncrementingEntityIdGenerator``
|
||||
|
||||
### Components
|
||||
|
||||
- ``Component``
|
||||
- ``ComponentAdded``
|
||||
- ``ComponentRemoved``
|
||||
- ``ComponentProvider``
|
||||
- ``ComponentsBuilder-4co42``
|
||||
- ``ComponentsBuilder``
|
||||
- ``ComponentInstanceProvider``
|
||||
- ``ComponentIdentifier``
|
||||
- ``ComponentInitializable``
|
||||
- ``ComponentTypeHash``
|
||||
- ``ComponentTypeProvider``
|
||||
- ``ComponentSingletonProvider``
|
||||
- ``SingleComponent``
|
||||
- ``EntityComponentHash``
|
||||
- ``StateComponentMapping``
|
||||
- ``DynamicComponentProvider``
|
||||
- ``RequiringComponents1``
|
||||
- ``RequiringComponents2``
|
||||
- ``RequiringComponents3``
|
||||
- ``RequiringComponents4``
|
||||
- ``RequiringComponents5``
|
||||
- ``RequiringComponents6``
|
||||
- ``RequiringComponents7``
|
||||
- ``RequiringComponents8``
|
||||
- ``DefaultInitializable``
|
||||
- ``SingleComponent``
|
||||
|
||||
### Systems
|
||||
|
||||
- ``Family``
|
||||
- ``FamilyEncoding``
|
||||
- ``FamilyDecoding``
|
||||
- ``FamilyMemberAdded``
|
||||
- ``FamilyMemberRemoved``
|
||||
- ``FamilyMemberBuilder-3f2i6``
|
||||
- ``FamilyMemberBuilder``
|
||||
- ``FamilyTraitSet``
|
||||
- ``Requires1``
|
||||
- ``Requires2``
|
||||
- ``Requires3``
|
||||
- ``Requires4``
|
||||
- ``Requires5``
|
||||
- ``Requires6``
|
||||
- ``Requires7``
|
||||
- ``Requires8``
|
||||
- ``Single``
|
||||
- ``Family1``
|
||||
- ``Family2``
|
||||
- ``Family3``
|
||||
- ``Family4``
|
||||
- ``Family5``
|
||||
- ``Family6``
|
||||
- ``Family7``
|
||||
- ``Family8``
|
||||
- ``FamilyRequirementsManaging``
|
||||
|
||||
### Coding Strategies
|
||||
|
||||
- ``CodingStrategy``
|
||||
- ``DefaultCodingStrategy``
|
||||
- ``TopLevelDecoder``
|
||||
- ``TopLevelEncoder``
|
||||
- ``DynamicCodingKey``
|
||||
|
||||
### Supporting Types
|
||||
|
||||
- ``ManagedContiguousArray``
|
||||
- ``UnorderedSparseSet``
|
||||
|
||||
### Hash Functions
|
||||
|
||||
- ``hash(combine:)``
|
||||
- ``hash(combine:_:)``
|
||||
- ``StringHashing``
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
# Getting started with Fireblade ECS
|
||||
|
||||
Learn the API and key types Fireblade provides to compose your game or app logic.
|
||||
|
||||
## Overview
|
||||
|
||||
Fireblade ECS is a dependency free, Swift language implementation of an Entity-Component-System ([ECS](https://en.wikipedia.org/wiki/Entity_component_system)).
|
||||
An ECS comprises entities composed from components of data, with systems which operate on the components.
|
||||
|
||||
Extend the following lines in your `Package.swift` file or use it to create a new project.
|
||||
|
||||
```swift
|
||||
// swift-tools-version:5.8
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "YourPackageName",
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.5")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "YourTargetName",
|
||||
dependencies: ["FirebladeECS"])
|
||||
]
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
This article introduces you to the key concepts of Fireblade ECS's API.
|
||||
For a more detailed example, see the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo).
|
||||
|
||||
### 🏛️ Nexus
|
||||
|
||||
The core element in the Fireblade-ECS is the [Nexus](https://en.wiktionary.org/wiki/nexus#Noun).
|
||||
It acts as a centralized way to store, access and manage entities and their components.
|
||||
A single `Nexus` may (theoretically) hold up to 4294967295 `Entities` at a time.
|
||||
You may use more than one `Nexus` at a time.
|
||||
|
||||
Initialize a `Nexus` with
|
||||
|
||||
```swift
|
||||
let nexus = Nexus()
|
||||
```
|
||||
|
||||
### 👤 Entities
|
||||
|
||||
then create entities by letting the `Nexus` generate them.
|
||||
|
||||
```swift
|
||||
// an entity without components
|
||||
let newEntity = nexus.createEntity()
|
||||
```
|
||||
|
||||
To define components, conform your class to the `Component` protocol
|
||||
|
||||
```swift
|
||||
final class Position: Component {
|
||||
var x: Int = 0
|
||||
var y: Int = 0
|
||||
}
|
||||
```
|
||||
and assign instances of it to an `Entity` with
|
||||
|
||||
```swift
|
||||
let position = Position(x: 1, y: 2)
|
||||
entity.assign(position)
|
||||
```
|
||||
|
||||
You can be more efficient by assigning components while creating an entity.
|
||||
|
||||
```swift
|
||||
// an entity with two components assigned.
|
||||
nexus.createEntity {
|
||||
Position(x: 1, y: 2)
|
||||
Color(.red)
|
||||
}
|
||||
|
||||
// bulk create entities with multiple components assigned.
|
||||
nexus.createEntities(count: 100) { _ in
|
||||
Position()
|
||||
Color()
|
||||
}
|
||||
|
||||
```
|
||||
### 👪 Families
|
||||
|
||||
This ECS uses a grouping approach for entities with the same component types to optimize cache locality and ease up access to them.
|
||||
Entities with the __same component types__ may belong to one `Family`.
|
||||
A `Family` has entities as members and component types as family traits.
|
||||
|
||||
Create a family by calling `.family` with a set of traits on the nexus.
|
||||
A family that contains only entities with a `Movement` and `PlayerInput` component, but no `Texture` component is created by
|
||||
|
||||
```swift
|
||||
let family = nexus.family(requiresAll: Movement.self, PlayerInput.self,
|
||||
excludesAll: Texture.self)
|
||||
```
|
||||
|
||||
These entities are cached in the nexus for efficient access and iteration.
|
||||
Families conform to the [Sequence](https://developer.apple.com/documentation/swift/sequence) protocol so that members (components)
|
||||
may be iterated and accessed like any other sequence in Swift.
|
||||
Access a family's components directly on the family instance. To get family entities and access components at the same time call `family.entityAndComponents`.
|
||||
If you are only interested in a family's entities call `family.entities`.
|
||||
|
||||
```swift
|
||||
class PlayerMovementSystem {
|
||||
let family = nexus.family(requiresAll: Movement.self, PlayerInput.self,
|
||||
excludesAll: Texture.self)
|
||||
|
||||
func update() {
|
||||
family
|
||||
.forEach { (mov: Movement, input: PlayerInput) in
|
||||
|
||||
// position & velocity component for the current entity
|
||||
|
||||
// get properties
|
||||
_ = mov.position
|
||||
_ = mov.velocity
|
||||
|
||||
// set properties
|
||||
mov.position.x = mov.position.x + 3.0
|
||||
...
|
||||
|
||||
// current input command for the given entity
|
||||
_ = input.command
|
||||
...
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func update2() {
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, mov: Movement, input: PlayerInput) in
|
||||
|
||||
// the current entity instance
|
||||
_ = entity
|
||||
|
||||
// position & velocity component for the current entity
|
||||
|
||||
// get properties
|
||||
_ = mov.position
|
||||
_ = mov.velocity
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func update3() {
|
||||
family
|
||||
.entities
|
||||
.forEach { (entity: Entity) in
|
||||
|
||||
// the current entity instance
|
||||
_ = entity
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🧑 Singles
|
||||
|
||||
A `Single` on the other hand is a special kind of family that holds exactly **one** entity with exactly **one** component for the entire lifetime of the Nexus. This may come in handy if you have components that have a [Singleton](https://en.wikipedia.org/wiki/Singleton_(mathematics)) character. Single components must conform to the `SingleComponent` protocol and will not be available through regular family iteration.
|
||||
|
||||
```swift
|
||||
final class GameState: SingleComponent {
|
||||
var quitGame: Bool = false
|
||||
}
|
||||
class GameLogicSystem {
|
||||
let gameState: Single<GameState>
|
||||
|
||||
init(nexus: Nexus) {
|
||||
gameState = nexus.single(GameState.self)
|
||||
}
|
||||
|
||||
func update() {
|
||||
// update your game sate here
|
||||
gameState.component.quitGame = true
|
||||
|
||||
// entity access is provided as well
|
||||
_ = gameState.entity
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 🔗 Serialization
|
||||
|
||||
|
||||
To serialize/deserialize entities you must conform their assigned components to the `Codable` protocol.
|
||||
Conforming components can then be serialized per family like this:
|
||||
|
||||
```swift
|
||||
// MyComponent and YourComponent both conform to Component and Codable protocols.
|
||||
let nexus = Nexus()
|
||||
let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self)
|
||||
|
||||
// JSON encode entities from given family.
|
||||
var jsonEncoder = JSONEncoder()
|
||||
let encodedData = try family.encodeMembers(using: &jsonEncoder)
|
||||
|
||||
// Decode entities into given family from JSON.
|
||||
// The decoded entities will be added to the nexus.
|
||||
var jsonDecoder = JSONDecoder()
|
||||
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -21,8 +21,10 @@ public typealias ComponentTypeHash = Int
|
|||
|
||||
// MARK: - hash combine
|
||||
|
||||
/// Calculates the combined hash of two values. This implementation is based on boost::hash_combine.
|
||||
/// Will always produce the same result for the same combination of seed and value during the single run of a program.
|
||||
/// Calculates the combined hash of two values.
|
||||
///
|
||||
/// This implementation is based on boost::hash_combine.
|
||||
/// It produces the same result for the same combination of seed and value during the single run of a program.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - seed: seed hash.
|
||||
|
|
@ -46,8 +48,10 @@ public func hash(combine seed: Int, _ value: Int) -> Int {
|
|||
return Int(bitPattern: uSeed)
|
||||
}
|
||||
|
||||
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
|
||||
/// Is sensitive to the order of the elements.
|
||||
/// Calculates the combined hash value of the elements.
|
||||
///
|
||||
/// This implementation is based on boost::hash_range.
|
||||
/// The hash value this method computes is sensitive to the order of the elements.
|
||||
/// - Parameter hashValues: sequence of hash values to combine.
|
||||
/// - Returns: combined hash value.
|
||||
public func hash<H: Sequence>(combine hashValues: H) -> Int where H.Element: Hashable {
|
||||
|
|
@ -79,11 +83,13 @@ extension EntityComponentHash {
|
|||
|
||||
// MARK: - string hashing
|
||||
|
||||
/// <https://stackoverflow.com/a/52440609>
|
||||
/// A type that provides stable hash values for String.
|
||||
///
|
||||
/// The details are based on [StackOverflow Q&A on String hashing in Swift](https://stackoverflow.com/a/52440609)
|
||||
public enum StringHashing {
|
||||
/// *Waren Singer djb2*
|
||||
/// *Warren Stringer djb2*
|
||||
///
|
||||
/// <https://stackoverflow.com/a/43149500>
|
||||
/// Implementation from <https://stackoverflow.com/a/43149500>
|
||||
public static func singer_djb2(_ utf8String: String) -> UInt64 {
|
||||
var hash: UInt64 = 5381
|
||||
var iter = utf8String.unicodeScalars.makeIterator()
|
||||
|
|
@ -95,8 +101,8 @@ public enum StringHashing {
|
|||
|
||||
/// *Dan Bernstein djb2*
|
||||
///
|
||||
/// This algorithm (k=33) was first reported by dan bernstein many years ago in comp.lang.c.
|
||||
/// Another version of this algorithm (now favored by bernstein) uses xor: hash(i) = hash(i - 1) * 33 ^ str[i];
|
||||
/// This algorithm (k=33) was first reported by Dan Bernstein many years ago in `comp.lang.c`.
|
||||
/// Another version of this algorithm (now favored by Bernstein) uses xor: `hash(i) = hash(i - 1) * 33 ^ str[i];`
|
||||
/// The magic of number 33 (why it works better than many other constants, prime or not) has never been adequately explained.
|
||||
///
|
||||
/// <http://www.cse.yorku.ca/~oz/hash.html>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Christian Treffs on 28.10.17.
|
||||
//
|
||||
/// A type that provides a managed contiguous array of elements that you provide.
|
||||
public struct ManagedContiguousArray<Element> {
|
||||
public typealias Index = Int
|
||||
|
||||
|
|
@ -11,16 +12,24 @@ public struct ManagedContiguousArray<Element> {
|
|||
@usableFromInline var size: Int = 0
|
||||
@usableFromInline var store: ContiguousArray<Element?> = []
|
||||
|
||||
/// Creates a new array.
|
||||
/// - Parameter minCount: The minimum number of elements, which defaults to `4096`.
|
||||
public init(minCount: Int = 4096) {
|
||||
chunkSize = minCount
|
||||
store = ContiguousArray<Element?>(repeating: nil, count: minCount)
|
||||
}
|
||||
|
||||
/// The number of elements in the array.
|
||||
@inline(__always)
|
||||
public var count: Int {
|
||||
size
|
||||
}
|
||||
|
||||
/// Inserts an element into the managed array.
|
||||
/// - Parameters:
|
||||
/// - element: The element to insert
|
||||
/// - index: The location at which to insert the element.
|
||||
/// - Returns: `true` to indicate the element was inserted.
|
||||
@discardableResult
|
||||
@inlinable
|
||||
public mutating func insert(_ element: Element, at index: Int) -> Bool {
|
||||
|
|
@ -34,6 +43,8 @@ public struct ManagedContiguousArray<Element> {
|
|||
return true
|
||||
}
|
||||
|
||||
/// Returns a Boolean value that indicates whether the index location holds an element.
|
||||
/// - Parameter index: The index location in the contiguous array to inspect.
|
||||
@inlinable
|
||||
public func contains(_ index: Index) -> Bool {
|
||||
if store.count <= index {
|
||||
|
|
@ -42,16 +53,25 @@ public struct ManagedContiguousArray<Element> {
|
|||
return store[index] != nil
|
||||
}
|
||||
|
||||
/// Retrieves the value at the index location you provide.
|
||||
/// - Parameter index: The index location.
|
||||
/// - Returns: The element at the index location, or `nil`.
|
||||
@inline(__always)
|
||||
public func get(at index: Index) -> Element? {
|
||||
store[index]
|
||||
}
|
||||
|
||||
/// Unsafely retrieves the value at the index location you provide.
|
||||
/// - Parameter index: The index location.
|
||||
/// - Returns: The element at the index location.
|
||||
@inline(__always)
|
||||
public func get(unsafeAt index: Index) -> Element {
|
||||
store[index].unsafelyUnwrapped
|
||||
}
|
||||
|
||||
/// Removes the object at the index location you provide.
|
||||
/// - Parameter index: The index location.
|
||||
/// - Returns: `true` to indicate the element was removed.
|
||||
@discardableResult
|
||||
@inlinable
|
||||
public mutating func remove(at index: Index) -> Bool {
|
||||
|
|
@ -65,17 +85,23 @@ public struct ManagedContiguousArray<Element> {
|
|||
return true
|
||||
}
|
||||
|
||||
/// Clears the array of all elements.
|
||||
/// - Parameter keepingCapacity: A Boolean value that indicates whether to keep the capacity of the array.
|
||||
@inlinable
|
||||
public mutating func clear(keepingCapacity: Bool = false) {
|
||||
size = 0
|
||||
store.removeAll(keepingCapacity: keepingCapacity)
|
||||
}
|
||||
|
||||
/// Returns a Boolean value that indicates if the array needs to grow to insert another item.
|
||||
/// - Parameter index: The index location to check.
|
||||
@inlinable
|
||||
func needsToGrow(_ index: Index) -> Bool {
|
||||
index > store.count - 1
|
||||
}
|
||||
|
||||
/// Expands the contiguous array to encompass the index location you provide.
|
||||
/// - Parameter index: The index location.
|
||||
@inlinable
|
||||
mutating func grow(to index: Index) {
|
||||
let newCapacity: Int = calculateCapacity(to: index)
|
||||
|
|
@ -83,6 +109,8 @@ public struct ManagedContiguousArray<Element> {
|
|||
store += ContiguousArray<Element?>(repeating: nil, count: newCount)
|
||||
}
|
||||
|
||||
/// Returns the capacity of the array to the index location you provide.
|
||||
/// - Parameter index: The index location
|
||||
@inlinable
|
||||
func calculateCapacity(to index: Index) -> Int {
|
||||
let delta = Float(index) / Float(chunkSize)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// Created by Christian Treffs on 30.10.17.
|
||||
//
|
||||
|
||||
/// An (unordered) sparse set.
|
||||
/// An unordered sparse set.
|
||||
///
|
||||
/// - `Element`: the element (instance) to store.
|
||||
/// - `Key`: the unique, hashable datastructure to use as a key to retrieve
|
||||
|
|
@ -130,6 +130,7 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new sparse set.
|
||||
public init() {
|
||||
self.init(storage: Storage())
|
||||
}
|
||||
|
|
@ -141,21 +142,26 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
|||
|
||||
@usableFromInline let storage: Storage
|
||||
|
||||
/// The size of the set.
|
||||
public var count: Int { storage.count }
|
||||
/// A Boolean value that indicates whether the set is empty.
|
||||
public var isEmpty: Bool { storage.isEmpty }
|
||||
|
||||
/// Returns a Boolean value that indicates whether the key is included in the set.
|
||||
/// - Parameter key: The key to inspect.
|
||||
@inlinable
|
||||
public func contains(_ key: Key) -> Bool {
|
||||
storage.findIndex(at: key) != nil
|
||||
}
|
||||
|
||||
/// Inset an element for a given key into the set in O(1).
|
||||
///
|
||||
/// Elements at previously set keys will be replaced.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - element: the element
|
||||
/// - key: the key
|
||||
/// - Returns: true if new, false if replaced.
|
||||
/// - element: The element.
|
||||
/// - key: The key.
|
||||
/// - Returns: `true` if new, `false` if replaced.
|
||||
@discardableResult
|
||||
public func insert(_ element: Element, at key: Key) -> Bool {
|
||||
storage.insert(element, at: key)
|
||||
|
|
@ -163,13 +169,16 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
|||
|
||||
/// Get the element for the given key in O(1).
|
||||
///
|
||||
/// - Parameter key: the key
|
||||
/// - Returns: the element or nil of key not found.
|
||||
/// - Parameter key: The key.
|
||||
/// - Returns: the element or `nil` if the key wasn't found.
|
||||
@inlinable
|
||||
public func get(at key: Key) -> Element? {
|
||||
storage.findElement(at: key)
|
||||
}
|
||||
|
||||
/// Unsafely gets the element for the given key,
|
||||
/// - Parameter key: The key.
|
||||
/// - Returns: The element.
|
||||
@inlinable
|
||||
public func get(unsafeAt key: Key) -> Element {
|
||||
storage.findElement(at: key).unsafelyUnwrapped
|
||||
|
|
@ -184,17 +193,21 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
|||
storage.remove(at: key)?.element
|
||||
}
|
||||
|
||||
/// Removes all keys and elements from the set.
|
||||
/// - Parameter keepingCapacity: A Boolean value that indicates whether the set should maintain it's capacity.
|
||||
@inlinable
|
||||
public func removeAll(keepingCapacity: Bool = false) {
|
||||
storage.removeAll(keepingCapacity: keepingCapacity)
|
||||
}
|
||||
|
||||
/// The first element of the set.
|
||||
@inlinable public var first: Element? {
|
||||
storage.first
|
||||
}
|
||||
}
|
||||
|
||||
extension UnorderedSparseSet where Key == Int {
|
||||
/// Retrieve or set an element using the key.
|
||||
@inlinable
|
||||
public subscript(key: Key) -> Element {
|
||||
get {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,94 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":combinePatchMinorReleases",
|
||||
":rebaseStalePrs",
|
||||
":renovatePrefix",
|
||||
"customManagers:githubActionsVersions"
|
||||
],
|
||||
"prConcurrentLimit": 5,
|
||||
"prHourlyLimit": 1,
|
||||
"recreateWhen": "auto",
|
||||
"prCreation": "not-pending",
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Accumulate non-major updates into one pull request",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchCurrentVersion": ">=1",
|
||||
"groupName": "all non-major dependencies",
|
||||
"groupSlug": "all-minor-patch"
|
||||
"groupName": "Swift non-major dependencies",
|
||||
"description": "Group all Swift package minor and patch updates together",
|
||||
"matchManagers": [
|
||||
"swift"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"renovate",
|
||||
"swift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "GitHub Actions non-major dependencies",
|
||||
"description": "Group all GitHub Actions minor and patch updates together",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"renovate",
|
||||
"ci"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "Mint non-major dependencies",
|
||||
"description": "Group all Mint package minor and patch updates together",
|
||||
"matchManagers": [
|
||||
"mint"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"renovate",
|
||||
"mint"
|
||||
]
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Update swift-tools-version in Package.swift",
|
||||
"fileMatch": [
|
||||
"Package\\.swift"
|
||||
],
|
||||
"matchStrings": [
|
||||
"^\/\/(\\s)?swift-tools-version:[\\s]?(?<currentValue>[0-9.]+)"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "swiftlang/swift",
|
||||
"extractVersionTemplate": "^swift-(?<version>.*)-RELEASE$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Update .swift-version",
|
||||
"fileMatch": ".swift-version",
|
||||
"matchStrings": [
|
||||
"(?<currentValue>[0-9.]+)"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "swiftlang/swift",
|
||||
"extractVersionTemplate": "^swift-(?<version>.*)-RELEASE$"
|
||||
}
|
||||
],
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"renovate"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue