Compare commits

...

86 Commits

Author SHA1 Message Date
renovate[bot] 5a7e7f6f2c
Update dependency ordo-one/package-benchmark to from: "1.29.2" (#96)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 23:36:49 +01:00
renovate[bot] f4171fe60b
Update compnerd/gha-setup-swift action to v0.3.0 (#101)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 23:28:29 +01:00
Christian Treffs a834762450
Update WASM CI 2025-03-07 23:21:41 +01:00
Christian Treffs 2a81e3f700
Update CI 2025-03-07 23:17:11 +01:00
Christian Treffs 95a68d3be5
Refine renovate.json 2025-03-07 23:05:01 +01:00
Christian Treffs bdf02b7764
Update CI 2025-03-07 22:55:09 +01:00
Christian Treffs b1e8c310c5
Update 2025-03-07 22:50:17 +01:00
Christian Treffs fe373e2c3f
Update renovate.json 2025-03-07 22:41:11 +01:00
Christian Treffs 8a1e19dc11
Update renovate.json 2025-03-07 22:33:20 +01:00
Christian Treffs a77d5eb257
Update renovate.json 2025-03-07 22:25:02 +01:00
Christian Treffs f69d47a87e
Refine customManager 2025-03-07 22:12:37 +01:00
Christian Treffs 616d9979e0
Move to customManagers 2025-03-07 22:05:32 +01:00
Christian Treffs 1b8039809d
Renovate swift-tools-version 2025-03-07 21:56:43 +01:00
Christian Treffs 36c2df79e4
Make versions explicit 2025-03-07 21:50:04 +01:00
renovate[bot] 77719f1bde
Update GitHub Actions non-major dependencies (#98)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 21:39:01 +01:00
Christian Treffs 102e97cc9a
Update renovate.json 2025-03-07 19:08:41 +01:00
Christian Treffs 2aee784f51
Update renovate.json 2025-03-07 19:06:06 +01:00
Christian Treffs c56a9f7155
Update renovate.json 2025-03-07 19:00:04 +01:00
Christian Treffs 240688a4e2
Update renovate.json 2025-03-07 18:41:56 +01:00
renovate[bot] 713d0fa3b3
Migrate config renovate.json (#93)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 18:23:38 +01:00
Christian Treffs e70179b362
Update renovate 2025-03-07 18:19:19 +01:00
Christian Treffs 58377bcfbc
Update renovate.json (#90) 2025-03-07 17:51:54 +01:00
Christian Treffs bcee9db3de
Maintenance (#83)
* Update JamesIves/github-pages-deploy-action action to v4.6.9

* Update codecov/codecov-action action to v5

* Update dependency nicklockwood/SwiftFormat to v0.55.0

* Migrate codecov

* Update macOS matrix

* Update macOS job strategy matrix

* Rename jobs

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-17 12:37:25 +01:00
Christian Treffs 2403d96e90
Fix documentation workflow 2024-11-01 10:12:54 +01:00
Christian Treffs ece8921cfe
Fix workflow 2024-11-01 10:10:09 +01:00
Christian Treffs 693acdc032
Update doc workflow 2024-11-01 10:08:44 +01:00
Christian Treffs ae23509c4a
Fix hosting base path 2024-11-01 08:12:02 +01:00
Christian Treffs bc16465ae0
Fix doc hosting base path 2024-11-01 08:08:19 +01:00
Christian Treffs 3115243f55
Update doc workflow 2024-11-01 08:04:38 +01:00
Christian Treffs 759f562316
Update documentation workflow (#79)
* Remove documentation workflow

* Update docc plugin dependency

* Add documentation workflow

* Update hosting basepath

* Update JamesIves/github-pages-deploy-action@v4.6.8
2024-11-01 07:57:20 +01:00
Joseph Heck fdf08687d6
DocC documentation generation (#77)
* initial stub files to support DocC generation

* add dependency on docc-plugin to support local previewing of documentation

* adding preview documentation commands to view documentation

* adding documentation generation commands

* cleanup with precommit

* update to reference expected location, or how to preview

* cleaning up documentation for global hashing algorithms

* adding documentation for ManagedContiguousArray

* adding documentation for UnorderedSparseSet

* directly porting README getting started into an Essentials article
2024-11-01 06:52:41 +01:00
renovate[bot] 9708eba6cb
Update dependency ordo-one/package-benchmark to from: "1.27.3" (#75) 2024-10-18 07:17:18 +02:00
renovate[bot] d4e779fc66
Update dependency ordo-one/package-benchmark to from: "1.27.2" (#73)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-13 12:04:47 +02:00
Joseph Heck e0ee97bd7f
adding package-benchmark sub-project (#64) 2024-10-10 18:51:46 +02:00
renovate[bot] 0b3ba9241b
Update actions/upload-artifact action to v4.4.3 (#68)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-10 11:13:26 +02:00
Joseph Heck 85ed3e2862
marks sourcery stencils as excluded from swiftpm processing (#69)
Co-authored-by: Christian Treffs <ctreffs@gmail.com>
2024-10-10 11:08:53 +02:00
Christian Treffs 3ce36faf2d
Add support for fork PR workflows (#71)
* Conditionally run code coverage

* Update ci-macos.yml
2024-10-10 10:56:56 +02:00
Christian Treffs 11f46f354d
Raise minimum Swift version to 5.8 (#67)
* Raise Swift tools version to 5.8

* Update README

* Add Swift version file

* Format code for 5.8

* Update swiftformat config
2024-10-09 20:52:56 +02:00
Christian Treffs 2c1c5885ae
Modernize project (#66)
* Add Mintfile

* Update Makefile

* Use old swiftlint and swiftformat versions

* Lint

* Update SwiftLint and SwiftFormat Versions + Lint

* Re-add documented make rules

* Update sourcery to 2.2.5

* Update SwiftLint and Lint
2024-10-09 17:08:12 +02:00
Christian Treffs a5c98c4963
Dependency updates 2024-10 (#65)
* Update actions/checkout action to v4

* Update swiftwasm/swiftwasm-action action to v5.9

* Update codecov/codecov-action action to v4

* Update actions/upload-artifact action to v4

* Accumulate all non-major dependency updates into one pull request

* Update CI Xcode versions

* Update pipeline

* Swift 5.10 on windows

* Upload test failure artifacts

* Debug linux CI

* Always

* Swift test help line comment

* Linux testing

* Linux testing

* Upload artifacts

* Include hidden files

* Update testing

* Update tests

* Skip some performance tests on non Mac machines

* Remove XCTestManifest files

* Fix codecov on macOS

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 14:30:55 +02:00
renovate[bot] 7ad5a96f16
Update swiftwasm/swiftwasm-action action to v5.8 (#56) 2023-05-08 18:39:25 +02:00
renovate[bot] 88dc088cb7
Update codecov/codecov-action action to v3.1.3 (#55) 2023-04-21 07:36:02 +02:00
renovate[bot] 50e5256e7a
Update codecov/codecov-action action to v3 (#54)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 07:35:16 +00:00
renovate[bot] 4fd8aac469
Update actions/upload-artifact action to v3 (#53)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 07:31:46 +00:00
renovate[bot] 7b077419ed
Update swift Docker tag to v5.8.0 (#51)
* Update swift Docker tag to v5.8.0

* Delete .gitlab-ci.yml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Christian Treffs <ctreffs@gmail.com>
2023-04-20 09:21:22 +02:00
renovate[bot] e7151356b8
Add renovate.json (#48)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 09:20:02 +02:00
Christian Treffs 7c60425de0
Update CI (#49)
* Update ci-windows.yml

* Update ci-macos.yml

* Update ci-wasm.yml

* Update ci-macos.yml

* Update ci-linux.yml

* Update documentation.yml
2023-04-20 07:19:00 +00:00
Christian Treffs 6f1ddd2bc4
Update macOS CI (#45)
* Move to macOS 11

* Use Xcode 13.0

* Remove .swift-version
2021-09-21 14:09:06 +00:00
Christian Treffs fd57037a6d
Rename @_functionBuilder to @resultBuilder for Swift 5.4+ (#44)
* Rename @_functionBuilder to @resultBuilder for Swift 5.4+

* Pin codecov version
2021-07-16 07:55:39 +02:00
Christian Treffs a993671120
Upload artifacts only on failure 2021-06-11 15:21:53 +02:00
Christian Treffs af32c8d0fd
Upload artifacts (#43)
* Upload artifacts

* Update ci-macos.yml

* Update ci-macos.yml

* Update CI
2021-06-11 12:56:05 +02:00
Christian Treffs 2aaef3d111
Update CI (#42)
* Split CI actions into dedicated files

* Fix pipelines

* Rename pipeline jobs

* Update README
2021-06-10 02:03:03 +02:00
Christian Treffs 0161470415
Rename .github/PULL_REQUEST_TEMPLATE/pull_request_template.md to .github/pull_request_template.md 2021-05-03 10:24:12 +02:00
Christian Treffs 4a87967722
Update pull_request_template.md 2021-05-03 08:20:19 +02:00
Christian Treffs 1bec85a3dc
Update CONTRIBUTING.md 2021-05-03 08:19:44 +02:00
Christian Treffs f2fbeedc89
Update and rename PULL_REQUEST_TEMPLATE.md to pull_request_template.md
Fix CONTRIBUTING.md link
2021-05-03 08:17:01 +02:00
Christian Treffs c9739f69ff
Merge pull request #41 from fireblade-engine/community
Add community files
2021-05-03 08:12:10 +02:00
Christian Treffs 68daff2b6d
Add community files 2021-05-03 08:04:46 +02:00
Christian Treffs 5f71f64626
Merge pull request #39 from fireblade-engine/feature/funding
Add funding
2020-12-03 11:40:17 +01:00
Christian Treffs 541e21d9c9
Update FUNDING.yml 2020-12-03 11:34:27 +01:00
Christian Treffs ddd676514b
Add FUNDING.yml 2020-12-03 11:29:41 +01:00
Christian Treffs c997e07817
Update CI 2020-11-25 13:12:51 +01:00
Christian Treffs 498f5baf33
Update README 2020-11-25 13:09:05 +01:00
Christian Treffs e1f3078ba5
Merge pull request #37 from fireblade-engine/bugfix/issue-36-component-added-event
Bugfix/Issue #36: ComponentAdded event
2020-11-25 11:26:08 +01:00
Christian Treffs c0dae11e23
Fix bulk entity creation with component builder 2020-11-25 11:16:53 +01:00
Christian Treffs 1737af9a71
Add NexusEventDelegate tests + lint 2020-11-25 11:08:30 +01:00
Christian Treffs e3d416a3e2
Fix ComponentAdded event emission for multi-component assigns (gh issue #36) 2020-11-25 11:08:08 +01:00
Christian Treffs f8915f1080
Remove unused events 2020-11-25 11:06:50 +01:00
Christian Treffs f642d81407
Update Makefile 2020-11-21 22:15:23 +01:00
Christian Treffs 0c6e33f634
Merge tag '0.17.3' into develop
Entity enhancements
2020-10-20 14:54:12 +02:00
Christian Treffs 78f690c910
Merge branch 'release/0.17.3' into master 2020-10-20 14:53:55 +02:00
Christian Treffs 09556e3786
Rename swiftwasm/swiftwasm-action@master to @main 2020-10-20 14:46:31 +02:00
Christian Treffs cfc92096b2
Update README 2020-10-20 14:42:29 +02:00
Christian Treffs d751359ea0
Add tests 2020-10-20 14:40:01 +02:00
Christian Treffs 0d66a30544
Refine component assignment accessors 2020-10-20 14:39:50 +02:00
Christian Treffs 788c6f75ea
Add entity creation convenience to Entity 2020-10-20 14:39:15 +02:00
Christian Treffs c4d48202be
Remove allComponents() and refine ComponentsIterator 2020-10-20 14:38:47 +02:00
Christian Treffs a8c0d94e44
Make entity value getter and setter optional compatible 2020-10-20 14:38:18 +02:00
Christian Treffs 27bad0e740
Merge tag '0.17.2' into develop
Entity advancements
2020-10-19 17:49:30 +02:00
Christian Treffs 450e494f0d
Merge branch 'release/0.17.2' into master 2020-10-19 17:49:20 +02:00
Christian Treffs 973349782c
Update README 2020-10-19 17:49:14 +02:00
Christian Treffs a60c6304fa
Merge pull request #34 from fireblade-engine/feature/entity-advancements
Entity advancements
2020-10-19 17:46:56 +02:00
Christian Treffs 8821648d7f
Refine component getter and setter 2020-10-19 17:33:53 +02:00
Christian Treffs bcf934ff7e
Add value getter and setter to entity 2020-10-19 17:12:45 +02:00
Christian Treffs 65b0644b5c
Conform all iterators to LazySequenceProtocol and Sequence 2020-10-19 16:08:44 +02:00
Christian Treffs 1622b06815
Merge tag '0.17.1' into develop
Add EntitiesIterator
2020-10-15 22:29:36 +02:00
66 changed files with 2218 additions and 989 deletions

View File

@ -1,5 +1,6 @@
ignore:
- "Tests/"
- "Benchmarks/"
comment:
layout: header, changes, diff
layout: header, changes, diff

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: [ctreffs]
custom: ['https://www.paypal.com/donate?hosted_button_id=GCG3K54SKRALQ']

61
.github/ISSUE_TEMPLATE/BUG_REPORT.md vendored Normal file
View File

@ -0,0 +1,61 @@
---
name: 🐛 Bug Report
about: Something isn't working as expected, create a report to help us improve
labels: bug
---
<!--
Thanks for contributing to this project!
Before you submit your issue, please replace each paragraph
below with the relevant details for your bug, and complete
the steps in the checklist by placing an 'x' in each box:
- [x] I've completed this task
- [ ] This task isn't completed
-->
### Bug Description
*A clear and concise description of what the bug is.
Replace this paragraph with a short description of the incorrect behavior.
(If this is a regression, please note the last version of the package that exhibited the correct behavior in addition to your current version.)*
### Information
- **Package version:** What tag or branch of this package are you using? e.g. tag `1.2.3` or branch `main`
- **Platform version:** Please tell us the version number of your operating system. e.g. `macOS 11.2.3` or `Ubuntu 20.04`
- **Swift version:** Paste the output of `swift --version` here.
### Checklist
- [ ] If possible, I've reproduced the issue using the `main`/`master` branch of this package.
- [ ] I've searched for existing issues under the issues tab.
- [ ] The bug is reproducible
### Steps to Reproduce
*Steps to reproduce the behavior:*
1. Go to '...'
2. '....'
*Replace this paragraph with an explanation of how to reproduce the incorrect behavior.
Include a simple code example, if possible.*
### Expected behavior
*A clear and concise description of what you expected to happen.
Describe what you expect to happen.*
### Actual behavior
*Describe or copy/paste the behavior you observe.*
### Screenshots
If applicable, add screenshots to help explain your problem.
### Additional context
*Add any other context about the problem here.*

View File

@ -0,0 +1,31 @@
---
name: 💡 Feature Request
about: A suggestion for a new feature or idea for this project
labels: enhancement
---
<!--
Thanks for contributing to this project!
Before you submit your issue, please replace the paragraphs
below with information about your proposed feature.
-->
### Feature request
*Replace this paragraph with a description of your proposed feature.
A clear and concise description of what the idea or problem is you want to solve.
Please be sure to describe some concrete use cases for the new feature -- be as specific as possible.
Provide links to existing issues or external references/discussions, if appropriate.*
### Describe the solution you'd like
*A clear and concise description of what you want to happen.*
### Describe alternatives you've considered
*A clear and concise description of any alternative solutions or features you've considered.*
### Additional context
*Add any other context or screenshots about the feature request here.*

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/JMM7W6pCCc
about: Questions or comments about using Fireblade? Ask here!

52
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,52 @@
<!--
Thanks for contributing to this project!
Before you submit your request, please replace each paragraph
below with the relevant details, and complete the steps in the
checklist by placing an 'x' in each box:
- [x] I've completed this task
- [ ] This task isn't completed
-->
### Description
*Replace this paragraph with a description of your changes and rationale.
Provide links to an existing issue or external references/discussions, if appropriate.*
### Detailed Design
*Include any additional information about the design here. At minimum, describe a synopsis of any public API additions.*
```swift
/// The new feature implemented by this pull request.
public struct Example: Collection {
}
```
### Documentation
*How has the new feature been documented?
Have the relevant portions of the guides in the Documentation folder been updated in addition to symbol-level documentation?*
### Testing
*How is the new feature tested?
Please ensure CI is not broken*
### Performance
*How did you verify the new feature performs as expected?*
### Source Impact
*What is the impact of this change on existing users of this package? Does it deprecate or remove any existing API?*
### Checklist
- [ ] I've read the [Contribution Guidelines](https://github.com/fireblade-engine/ecs/blob/master/CONTRIBUTING.md)
- [ ] I've followed the coding style of the rest of the project.
- [ ] I've added tests covering all new code paths my change adds to the project (to the extent possible).
- [ ] I've added benchmarks covering new functionality (if appropriate).
- [ ] I've verified that my change does not break any existing tests or introduce unexpected benchmark regressions.
- [ ] I've updated the documentation (if appropriate).

39
.github/workflows/ci-linux.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Linux
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
swift: ["latest"]
container:
image: swift:${{ matrix.swift }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test
run: swift test -c release --enable-xctest --parallel --xunit-output .build/xUnit-output.xml
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-artifacts-linux-${{ matrix.swift }}-${{ github.run_id }}
path: |
.build/*.yaml
.build/*.xml
.build/*.json
.build/*.txt
.build/**/*.xctest
.build/**/*.json
.build/**/*.txt
if-no-files-found: warn
include-hidden-files: true

62
.github/workflows/ci-macos.yml vendored Normal file
View File

@ -0,0 +1,62 @@
name: macOS
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODECOV_XCODE_VERSION: "16.0" # Xcode version used to generate code coverage
jobs:
macos:
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: true
matrix:
config:
- { os: "macos-14", xcode: "15.4" }
- { os: "macos-15", xcode: "16.0" }
steps:
- name: Checkout
uses: actions/checkout@v4
- 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.config.xcode }}.app/Contents/Developer
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-artifacts-${{ matrix.config.xcode }}-${{ github.run_id }}
path: |
.build/*.yaml
.build/*.xml
.build/*.json
.build/*.txt
.build/**/*.xctest
.build/**/*.json
.build/**/*.txt
if-no-files-found: warn
include-hidden-files: true
# 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.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.config.xcode == env.CODECOV_XCODE_VERSION
uses: codecov/codecov-action@v5.4.0
with:
token: ${{ env.CODECOV_TOKEN }}
files: coverage.lcov
fail_ci_if_error: true

16
.github/workflows/ci-wasm.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: WASM
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
wasm:
runs-on: ubuntu-latest
container: swift:6.0.3
steps:
- uses: actions/checkout@v4
- uses: swiftwasm/setup-swiftwasm@v2
- run: swift build --swift-sdk wasm32-unknown-wasi

47
.github/workflows/ci-windows.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Windows
on:
push:
branches: [ master ]
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:
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.3.0
with:
branch: swift-5.10-release
tag: 5.10-RELEASE
- name: Checkout
uses: actions/checkout@v4
- name: Test
run: swift test -c release --parallel --xunit-output .build/xUnit-output.xml
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-artifacts-windows-${{ github.run_id }}
path: |
.build/*.yaml
.build/*.xml
.build/*.json
.build/*.txt
.build/**/*.xctest
.build/**/*.json
.build/**/*.txt
if-no-files-found: warn
include-hidden-files: true

View File

@ -1,81 +0,0 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
macOS:
runs-on: macOS-latest
strategy:
matrix:
xcode: ["11.7", "12"]
steps:
- name: Checkout
uses: actions/checkout@master
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: Swift version
run: swift --version
- name: Test
run: swift test -v --skip-update --parallel --enable-test-discovery --enable-code-coverage
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
- name: Generate coverage report
run: xcrun llvm-cov export -format="lcov" .build/debug/*PackageTests.xctest/Contents/MacOS/*PackageTests -instr-profile .build/debug/codecov/default.profdata > coverage.lcov
- name: Upload code coverage report
uses: codecov/codecov-action@master
with:
token: ${{secrets.CODECOV_TOKEN}}
file: coverage.lcov
fail_ci_if_error: true
- name: Build Release
run: swift build -c release
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
linux:
runs-on: ubuntu-latest
strategy:
matrix:
swift: ["5.1", "latest"]
container:
image: swift:${{ matrix.swift }}
steps:
- name: Checkout
uses: actions/checkout@master
- name: "Update APT"
shell: bash
run: "apt update"
- name: "Install curl"
shell: bash
run: "apt-get install -y curl"
- name: Swift version
run: swift --version
- name: Test
run: swift test -v --skip-update --parallel --enable-test-discovery --enable-code-coverage
- name: Generate coverage report
run: llvm-cov export -format="lcov" .build/debug/*PackageTests.xctest -instr-profile .build/debug/codecov/default.profdata > coverage.lcov
- name: Upload code coverage report
uses: codecov/codecov-action@master
with:
token: ${{secrets.CODECOV_TOKEN}}
file: coverage.lcov
fail_ci_if_error: true
- name: Build Release
run: swift build -c release
webAssembly:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@master
- name: Swift version
run: swift --version
- name: Build
uses: swiftwasm/swiftwasm-action@master
with:
shell-action: swift build --triple wasm32-unknown-wasi

View File

@ -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 documentation
uses: SwiftDocOrg/swift-doc@master
- 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

View File

@ -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

View File

@ -1,25 +0,0 @@
image: swift:5.1.3
stages:
- test
- build_release
build_project:
stage: build_release
script:
- swift package reset
- swift build -c release
tags:
- docker
only:
- master
test_project:
stage: test
variables:
GIT_DEPTH: "50"
script:
- swift package reset
- swift test
tags:
- docker

4
.spi.yml Normal file
View File

@ -0,0 +1,4 @@
version: 1
builder:
configs:
- documentation_targets: [FirebladeECS]

View File

@ -1 +1 @@
5.1.3
5.8

13
.swiftformat Normal file
View File

@ -0,0 +1,13 @@
# file options
--exclude Sources/**/*.generated.swift
--exclude Sources/FirebladeECS/Entity+Component.swift # problems with self.get { }
--exclude Tests/**/*.swift
# format options
--extensionacl on-declarations
--stripunusedargs closure-only
--commas inline
--self remove
--selfrequired get
--disable preferKeyPath
--disable opaqueGenericParameters

View File

@ -9,23 +9,11 @@ identifier_name:
line_length: 220
number_separator:
minimum_length: 5
analyzer_rules:
- explicit_self
- unused_declaration
- unused_import
opt_in_rules:
#- explicit_acl
#- explicit_enum_raw_value
#- explicit_type_interface
#- extension_access_modifier
#- file_name
#- file_types_order
#- indentation_width
#- missing_docs
#- multiline_arguments_brackets
#- multiline_literal_brackets
#- multiline_parameters_brackets
#- no_grouping_extension
#- required_deinit
#- type_contents_order
#- unowned_variable_capture
- anyobject_protocol
- array_init
- attributes
- closure_body_length
@ -48,7 +36,6 @@ opt_in_rules:
- enum_case_associated_values_count
- expiring_todo
- explicit_init
- explicit_self
- explicit_top_level_acl
- fallthrough
- fatal_error_message
@ -109,8 +96,6 @@ opt_in_rules:
- unavailable_function
- unneeded_parentheses_in_closure_argument
- untyped_error_in_catch
- unused_declaration
- unused_import
- vertical_parameter_alignment_on_call
- vertical_whitespace_between_cases
- vertical_whitespace_closing_braces

View File

@ -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
}

View File

@ -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
}
)
}
}
}

28
Benchmarks/Package.swift Normal file
View File

@ -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")
]
)
]
)

22
Benchmarks/README.md Normal file
View File

@ -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).

12
CODEOWNERS Normal file
View File

@ -0,0 +1,12 @@
# This file is a list of the people responsible for ensuring that contributions
# to this projected are reviewed, either by themselves or by someone else.
# They are also the gatekeepers for their part of this project, with the final
# word on what goes in or not.
# The code owners file uses a .gitignore-like syntax to specify which parts of
# the codebase is associated with an owner. See
# <https://docs.github.com/github/creating-cloning-and-archiving-repositories/about-code-owners>
# for details.
# The following lines are used by GitHub to automatically recommend reviewers.
# Each line is a file pattern followed by one or more owners.
* @ctreffs

136
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,136 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement
e.g. via [content abuse report][ref-report-abuse].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][ref-homepage-cc],
version 2.0, available at
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>.
Translations are available at
<https://www.contributor-covenant.org/translations>.
<!-- REFERENCES -->
[ref-homepage-cc]: https://www.contributor-covenant.org
[ref-report-abuse]: https://docs.github.com/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request
[ref-gh-coc]: https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-code-of-conduct-to-your-project
[ref-gh-abuse]: https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-how-contributors-report-abuse-in-your-organizations-repository
[ref-coc-guide]: https://opensource.guide/code-of-conduct/

View File

@ -8,18 +8,18 @@
The following is a set of **guidelines for contributing** to this project.
Use your best judgment and feel free to propose changes to this document in a pull request.
**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
### 💡 Your contribution - the sky is the limit 🌈
This is an open source project and we love to receive contributions from our community — [**you**][ref-contributors]!
There are many ways to contribute, from writing __tutorials__ or __blog posts__, improving the [__documentation__][ref-documentation], submitting __bug reports__ and __feature requests__ or
__writing code__ which can be incorporated into the repository itself.
There are many ways to contribute, from writing __tutorials__ or __blog posts__, improving the [__documentation__][ref-documentation], submitting [__bug reports__][ref-issues-new] and [__enhancement__][ref-pull-request-new] or
[__writing code__][ref-pull-request-new] which can be incorporated into the repository itself.
When contributing to this project, please feel free to discuss the change you wish to make via issue with the repository owners before making a change.
When contributing to this project, please feel free to discuss the changes and ideas you wish to contribute with the repository owners before making a change by opening a [new issue][ref-issues-new] and add the **feature request** tag to that issue.
<!--Note that we have a [code of conduct][ref-code-of-conduct], please follow it in all your interactions with the project.-->
Note that we have a [code of conduct][ref-code-of-conduct], please follow it in all your interactions with the project.
### 🐞 You want to report a bug or file an issue?
@ -55,11 +55,12 @@ If you want to start somewhere, this would be a good place to start.
That said, these aren't necessarily the easiest tickets.
For any new contributions please consider these guidelines:
1. Open a [new pull request (PR)][ref-pull-request-new] with a **clear and descriptive title**
2. Write a **detailed comment** with as much relevant information as possible including:
- What your feature is intended to do?
- How it can be used?
- What alternatives where considered?
- What alternatives where considered, if any?
- Has this feature impact on performance or stability of the project?
#### Your contribution responsibilities
@ -68,7 +69,7 @@ Don't be intimidated by these responsibilities, they are easy to meet if you tak
- [x] Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback.
- [x] Ensure (cross-)platform compatibility for every change that's accepted. An addition should not reduce the number of platforms that the project supports.
- [x] Ensure **coding conventions** are met. Lint your code with the project's default tools.
- [x] Ensure **coding conventions** are met. Lint your code with the project's default tools. Project wide commands are available through the [Makefile][ref-makefile] in the repository root.
- [x] Add tests for your feature that prove it's working as expected. Code coverage should not drop below its previous value.
- [x] Ensure none of the existing tests are failing after adding your changes.
- [x] Document your public API code and ensure to add code comments where necessary.
@ -80,7 +81,7 @@ Please consult the [README][ref-readme] for installation instructions.
<!-- REFERENCES -->
[ref-code-of-conduct]: t.b.d.
[ref-code-of-conduct]: https://github.com/fireblade-engine/ecs/blob/master/CODE_OF_CONDUCT.md
[ref-contributors]: https://github.com/fireblade-engine/ecs/graphs/contributors
[ref-documentation]: https://github.com/fireblade-engine/ecs/wiki
[ref-gh-actions]: https://github.com/fireblade-engine/ecs/actions
@ -91,3 +92,4 @@ Please consult the [README][ref-readme] for installation instructions.
[ref-pull-request-how-to]: https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls
[ref-pull-request-new]: https://github.com/fireblade-engine/ecs/compare
[ref-readme]: https://github.com/fireblade-engine/ecs/blob/master/README.md
[ref-makefile]: https://github.com/fireblade-engine/ecs/blob/master/Makefile

132
Makefile
View File

@ -1,75 +1,95 @@
# Version 1.0.0
UNAME_S := $(shell uname -s)
SWIFT_PACKAGE_VERSION := $(shell swift package tools-version)
# Lint
lint:
swiftlint autocorrect --format
swiftlint lint --quiet
# Lint fix and format code.
.PHONY: lint-fix
swiftlint:
mint run swiftlint lint --fix --config .swiftlint.yml --format --quiet
swiftformat:
mint run swiftformat . --swiftversion ${SWIFT_PACKAGE_VERSION}
lint-fix: swiftlint swiftformat
lintErrorOnly:
@swiftlint autocorrect --format --quiet
@swiftlint lint --quiet | grep error
# Generate code
.PHONY: generate-code
generate-code:
mint run sourcery --quiet --config ./.sourcery.yml
mint run sourcery --quiet --config ./.sourceryTests.yml
# Git
precommit: generateCode generateTestsCode lint genLinuxTests
# Run pre-push tasks
.PHONY: pre-push
pre-push: generate-code lint-fix
submodule:
git submodule init
git submodule update --recursive
.PHONY: precommit
precommit: pre-push
# Tests
genLinuxTests:
swift test --generate-linuxmain
swiftlint autocorrect --format --path Tests/
.PHONY: setup-brew
setup-brew:
@which -s brew || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
@brew update
test: genLinuxTests
swift test
.PHONY: install-dependencies-macOS
install-dependencies-macOS: setup-brew
brew install mint
mint bootstrap
# Package
latest:
swift package update
.PHONY: setupEnvironment
setupEnvironment: install-dependencies-macOS
resolve:
swift package resolve
# Build debug version
.PHONY: build-debug
build-debug:
swift build -c debug
# Xcode
genXcode:
swift package generate-xcodeproj --enable-code-coverage --skip-extra-files
genXcodeOpen: genXcode
open *.xcodeproj
# Clean
clean:
swift package reset
rm -rdf .swiftpm/xcode
rm -rdf .build/
rm Package.resolved
rm .DS_Store
cleanArtifacts:
swift package clean
# Build release version
.PHONY: build-release
build-release:
swift build -c release --skip-update
# Test links in README
# requires <https://github.com/tcort/markdown-link-check>
.PHONY: testReadme
testReadme:
markdown-link-check -p -v ./README.md
generateCode:
sourcery --config ./.sourcery.yml --verbose
generateTestsCode:
sourcery --config ./.sourceryTests.yml --verbose
# Delete package build artifacts.
.PHONY: clean
clean: clean-sourcery
swift package clean
brewInstallDeps: brewUpdate
brew install swiftenv
brew install swiftlint
brew install sourcery
# Clean sourcery cache
.PHONY: clean-sourcery
clean-sourcery:
rm -rdf ${HOME}/Library/Caches/Sourcery
brewSetup:
which -s brew || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# Preview DocC documentation
.PHONY: preview-docs
preview-docs:
swift package --disable-sandbox preview-documentation --target FirebladeECS
brewUpdate: brewSetup
brew update
# 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
setupEnvironment: brewInstallDeps
open Package.swift
# 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'

3
Mintfile Normal file
View File

@ -0,0 +1,3 @@
realm/SwiftLint@0.57.0
nicklockwood/SwiftFormat@0.55.0
krzysztofzablocki/Sourcery@2.2.5

23
Package.resolved Normal file
View File

@ -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
}

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.8
import PackageDescription
let package = Package(
@ -7,10 +7,15 @@ 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"),
.target(name: "FirebladeECS",
exclude: ["Stencils/Family.stencil"]),
.testTarget(name: "FirebladeECSTests",
dependencies: ["FirebladeECS"]),
dependencies: ["FirebladeECS"],
exclude: ["Stencils/FamilyTests.stencil"]),
.testTarget(name: "FirebladeECSPerformanceTests",
dependencies: ["FirebladeECS"])
],

View File

@ -1,11 +1,13 @@
# Fireblade ECS (Entity-Component System)
[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs)
[![macOS](https://github.com/fireblade-engine/ecs/actions/workflows/ci-macos.yml/badge.svg)](https://github.com/fireblade-engine/ecs/actions/workflows/ci-macos.yml)
[![Linux](https://github.com/fireblade-engine/ecs/actions/workflows/ci-linux.yml/badge.svg)](https://github.com/fireblade-engine/ecs/actions/workflows/ci-linux.yml)
[![Windows](https://github.com/fireblade-engine/ecs/actions/workflows/ci-windows.yml/badge.svg)](https://github.com/fireblade-engine/ecs/actions/workflows/ci-windows.yml)
[![WASM](https://github.com/fireblade-engine/ecs/actions/workflows/ci-wasm.yml/badge.svg)](https://github.com/fireblade-engine/ecs/actions/workflows/ci-wasm.yml)
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
[![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs)
[![spi-swift-versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/fireblade-engine/ecs)
[![spi-swift-platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/fireblade-engine/ecs)
[![platform-webassembly](https://img.shields.io/badge/Platform-WebAssembly-blue.svg)](https://github.com/swiftwasm/swift#swiftwasm)
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. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine).
@ -23,19 +25,19 @@ These instructions will get you a copy of the project up and running on your loc
### 💻 Installing
Fireblade ECS is available for all platforms that support [Swift 5.1](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager).
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).
Extend the following lines in your `Package.swift` file or use it to create a new project.
```swift
// swift-tools-version:5.1
// 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.1")
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.5")
],
targets: [
.target(
@ -231,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

View File

@ -13,6 +13,6 @@ public struct DynamicCodingKey: CodingKey {
public var intValue: Int?
public var stringValue: String
public init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
public init?(intValue: Int) { self.intValue = intValue; stringValue = "\(intValue)" }
public init?(stringValue: String) { self.stringValue = stringValue }
}

View File

@ -13,15 +13,15 @@ public struct ComponentIdentifier {
extension ComponentIdentifier {
@usableFromInline
init<C>(_ componentType: C.Type) where C: Component {
self.id = Self.makeRuntimeHash(componentType)
init(_ componentType: (some Component).Type) {
id = Self.makeRuntimeHash(componentType)
}
/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Identifier where C: Component {
static func makeRuntimeHash(_ componentType: (some Component).Type) -> Identifier {
ObjectIdentifier(componentType).hashValue
}
}
extension ComponentIdentifier: Equatable { }
extension ComponentIdentifier: Hashable { }
extension ComponentIdentifier: Equatable {}
extension ComponentIdentifier: Hashable {}

View File

@ -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``

View File

@ -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)
```

View File

@ -8,12 +8,12 @@
extension Entity {
@inlinable
public func get<C>() -> C? where C: Component {
nexus.get(for: identifier)
nexus.get(safe: identifier)
}
@inlinable
public func get<A>(component compType: A.Type = A.self) -> A? where A: Component {
nexus.get(for: identifier)
nexus.get(safe: identifier)
}
@inlinable
@ -32,22 +32,124 @@ extension Entity {
return (compA, compB, compC)
}
/// Get or set component instance by type via subscript.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// the new instance will be assigned to this entity.
/// - If `Comp` is already assinged to this entity nothing happens.
/// - If `Comp` is set to `nil` and an instance of `Comp` is assigned to this entity,
/// `Comp` will be removed from this entity.
@inlinable
public subscript<C: Component, Value>(_ componentKeyPath: WritableKeyPath<C, Value>) -> Value? {
public subscript<Comp>(_ componentType: Comp.Type) -> Comp? where Comp: Component {
get { self.get(component: componentType) }
nonmutating set {
guard var comp = self.get(component: C.self),
let value = newValue else {
guard let newComponent = newValue else {
self.remove(Comp.self)
return
}
comp[keyPath: componentKeyPath] = value
}
get {
self.get(component: C.self)?[keyPath: componentKeyPath]
if self.get(component: componentType) === newComponent {
return
}
self.assign(newComponent)
}
}
/// Get the value of a component using the key Path to the property in the component.
///
/// A `Comp` instance must be assigned to this entity!
/// - Parameter componentKeyPath: The `KeyPath` to the property of the given component.
@inlinable
public subscript<C: Component>(_ componentType: C.Type) -> C? {
self.get(component: componentType)
public func get<Comp, Value>(valueAt componentKeyPath: KeyPath<Comp, Value>) -> Value where Comp: Component {
self.get(component: Comp.self)![keyPath: componentKeyPath]
}
/// Get the value of a component using the key Path to the property in the component.
///
/// A `Comp` instance must be assigned to this entity!
/// - Parameter componentKeyPath: The `KeyPath` to the property of the given component.
@inlinable
public func get<Comp, Value>(valueAt componentKeyPath: KeyPath<Comp, Value?>) -> Value? where Comp: Component {
self.get(component: Comp.self)![keyPath: componentKeyPath]
}
/// Get the value of a component using the key Path to the property in the component.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: KeyPath<Comp, Value>) -> Value where Comp: Component {
self.get(valueAt: componentKeyPath)
}
/// Get the value of a component using the key Path to the property in the component.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: KeyPath<Comp, Value?>) -> Value? where Comp: Component {
self.get(valueAt: componentKeyPath)
}
/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
///
/// - Parameters:
/// - newValue: The value to set.
/// - componentKeyPath: The `ReferenceWritableKeyPath` to the property of the given component.
/// - Returns: Returns true if an action was performed, false otherwise.
@inlinable
@discardableResult
public func set<Comp, Value>(value newValue: Value, for componentKeyPath: ReferenceWritableKeyPath<Comp, Value>) -> Bool where Comp: Component & DefaultInitializable {
guard has(Comp.self) else {
let newInstance = Comp()
newInstance[keyPath: componentKeyPath] = newValue
return nexus.assign(component: newInstance, entityId: identifier)
}
get(component: Comp.self)![keyPath: componentKeyPath] = newValue
return true
}
/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
///
/// - Parameters:
/// - newValue: The value to set.
/// - componentKeyPath: The `ReferenceWritableKeyPath` to the property of the given component.
/// - Returns: Returns true if an action was performed, false otherwise.
@inlinable
@discardableResult
public func set<Comp, Value>(value newValue: Value?, for componentKeyPath: ReferenceWritableKeyPath<Comp, Value?>) -> Bool where Comp: Component & DefaultInitializable {
guard has(Comp.self) else {
let newInstance = Comp()
newInstance[keyPath: componentKeyPath] = newValue
return nexus.assign(component: newInstance, entityId: identifier)
}
get(component: Comp.self)![keyPath: componentKeyPath] = newValue
return true
}
/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: ReferenceWritableKeyPath<Comp, Value>) -> Value where Comp: Component & DefaultInitializable {
get { self.get(valueAt: componentKeyPath) }
nonmutating set { self.set(value: newValue, for: componentKeyPath) }
}
/// Set the value of a component using the key path to the property in the component.
///
/// **Behavior:**
/// - If `Comp` is a component type that is currently *not* assigned to this entity,
/// a new instance of `Comp` will be default initialized and `newValue` will be set at the given keyPath.
@inlinable
public subscript<Comp, Value>(_ componentKeyPath: ReferenceWritableKeyPath<Comp, Value?>) -> Value? where Comp: Component & DefaultInitializable {
get { self.get(valueAt: componentKeyPath) }
nonmutating set { self.set(value: newValue, for: componentKeyPath) }
}
}

View File

@ -17,9 +17,9 @@ public struct Entity {
/// The unique entity identifier.
public private(set) var identifier: EntityIdentifier
internal init(nexus: Nexus, id: EntityIdentifier) {
init(nexus: Nexus, id: EntityIdentifier) {
self.nexus = nexus
self.identifier = id
identifier = id
}
/// Returns the number of components for this entity.
@ -27,9 +27,24 @@ public struct Entity {
nexus.count(components: identifier)
}
@discardableResult
public func createEntity() -> Entity {
nexus.createEntity()
}
@discardableResult
public func createEntity(with components: Component...) -> Entity {
createEntity(with: components)
}
@discardableResult
public func createEntity(with components: some Collection<Component>) -> Entity {
nexus.createEntity(with: components)
}
/// Checks if a component with given type is assigned to this entity.
/// - Parameter type: the component type.
public func has<C>(_ type: C.Type) -> Bool where C: Component {
public func has(_ type: (some Component).Type) -> Bool {
has(type.identifier)
}
@ -48,9 +63,7 @@ public struct Entity {
/// - Parameter components: one or more components.
@discardableResult
public func assign(_ components: Component...) -> Entity {
for component: Component in components {
assign(component)
}
assign(components)
return self
}
@ -65,22 +78,28 @@ public struct Entity {
/// Add a typed component to this entity.
/// - Parameter component: the typed component.
@discardableResult
public func assign<C>(_ component: C) -> Entity where C: Component {
nexus.assign(component: component, to: self)
public func assign(_ component: some Component) -> Entity {
assign(component)
return self
}
@discardableResult
public func assign(_ components: some Collection<Component>) -> Entity {
nexus.assign(components: components, to: self)
return self
}
/// Remove a component from this entity.
/// - Parameter component: the component.
@discardableResult
public func remove<C>(_ component: C) -> Entity where C: Component {
public func remove(_ component: some Component) -> Entity {
remove(component.identifier)
}
/// Remove a component by type from this entity.
/// - Parameter compType: the component type.
@discardableResult
public func remove<C>(_ compType: C.Type) -> Entity where C: Component {
public func remove(_ compType: (some Component).Type) -> Entity {
remove(compType.identifier)
}
@ -107,33 +126,27 @@ public struct Entity {
public func makeComponentsIterator() -> ComponentsIterator {
ComponentsIterator(nexus: nexus, entityIdentifier: identifier)
}
/// Returns a sequence of all componenents of this entity.
@inlinable
public func allComponents() -> AnySequence<Component> {
AnySequence { self.makeComponentsIterator() }
}
}
extension Entity {
public struct ComponentsIterator: IteratorProtocol {
private var iterator: AnyIterator<Component>
private var iterator: IndexingIterator<[Component]>?
@usableFromInline
init(nexus: Nexus, entityIdentifier: EntityIdentifier) {
if let comps = nexus.get(components: entityIdentifier) {
iterator = AnyIterator<Component>(comps.compactMap { nexus.get(component: $0, for: entityIdentifier) }.makeIterator())
} else {
iterator = AnyIterator { nil }
}
iterator = nexus.get(components: entityIdentifier)?
.map { nexus.get(unsafe: $0, for: entityIdentifier) }
.makeIterator()
}
public mutating func next() -> Component? {
iterator.next()
iterator?.next()
}
}
}
extension Entity.ComponentsIterator: Sequence { }
extension Entity.ComponentsIterator: LazySequenceProtocol {}
extension Entity.ComponentsIterator: Sequence {}
extension Entity: Equatable {
public static func == (lhs: Entity, rhs: Entity) -> Bool {

View File

@ -23,8 +23,8 @@ public struct EntityIdentifier {
}
}
extension EntityIdentifier: Equatable { }
extension EntityIdentifier: Hashable { }
extension EntityIdentifier: Equatable {}
extension EntityIdentifier: Hashable {}
extension EntityIdentifier: RawRepresentable {
/// The entity identifier represented as a raw value.
@ -33,7 +33,7 @@ extension EntityIdentifier: RawRepresentable {
@inlinable
public init(rawValue: Identifier) {
self.id = rawValue
id = rawValue
}
}

View File

@ -49,10 +49,10 @@ public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
@usableFromInline
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map { $0.id }
let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map(\.id)
let maxInUseValue = initialInUse.max() ?? 0
let inUseSet = Set(initialInUse) // a set of all eIds in use
let allSet = Set(0...maxInUseValue) // all eIds from 0 to including maxInUseValue
let allSet = Set(0 ... maxInUseValue) // all eIds from 0 to including maxInUseValue
let freeSet = allSet.subtracting(inUseSet) // all "holes" / unused / free eIds
let initialFree = Array(freeSet).sorted().reversed() // order them to provide them linear increasing after all initially used are provided.
stack = initialFree + initialInUse
@ -83,12 +83,12 @@ public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
@inlinable
public init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
self.storage = Storage(startProviding: initialEntityIds)
storage = Storage(startProviding: initialEntityIds)
}
@inlinable
public init() {
self.storage = Storage()
storage = Storage()
}
@inline(__always)

View File

@ -97,9 +97,7 @@ extension ComponentTypeProvider: ComponentProvider {
/// This component provider always returns the same instance of the component. The instance
/// is created when first required and is of the type passed in to the initializer.
public final class ComponentSingletonProvider {
private lazy var instance: Component = {
componentType.init()
}()
private lazy var instance: Component = componentType.init()
private var componentType: ComponentInitializable.Type
@ -171,7 +169,7 @@ extension DynamicComponentProvider: ComponentProvider {
/// Represents a state for an EntityStateMachine. The state contains any number of ComponentProviders which
/// are used to add components to the entity when this state is entered.
public class EntityState {
internal var providers = [ComponentIdentifier: ComponentProvider]()
var providers = [ComponentIdentifier: ComponentProvider]()
public init() {}
@ -253,7 +251,7 @@ extension EntityState {
/// - Returns: This EntityState, so more modifications can be applied.
@inline(__always)
@discardableResult
public func addProvider<C: ComponentInitializable>(type: C.Type, provider: ComponentProvider) -> Self {
public func addProvider(type: (some ComponentInitializable).Type, provider: ComponentProvider) -> Self {
addMapping(for: type).withProvider(provider)
return self
}
@ -272,7 +270,7 @@ public class StateComponentMapping {
/// by more specific mappings if other methods are called.
/// - Parameter creatingState: The EntityState that the mapping will belong to
/// - Parameter type: The component type for the mapping
internal init(creatingState: EntityState, type: ComponentInitializable.Type) {
init(creatingState: EntityState, type: ComponentInitializable.Type) {
self.creatingState = creatingState
componentType = type
provider = ComponentTypeProvider(type: type)
@ -318,7 +316,7 @@ public class StateComponentMapping {
/// - Parameter closure: The Closure instance to return the component instance
/// - Returns: This ComponentMapping, so more modifications can be applied
@discardableResult
public func withMethod<C: Component>(_ closure: DynamicComponentProvider<C>.Closure) -> Self {
public func withMethod(_ closure: DynamicComponentProvider<some Component>.Closure) -> Self {
setProvider(DynamicComponentProvider(closure: closure))
return self
}
@ -403,7 +401,7 @@ public class EntityStateMachine<StateIdentifier: Hashable> {
var toAdd: [ComponentIdentifier: ComponentProvider]
if let currentState = currentState {
if let currentState {
toAdd = .init()
for (identifier, provider) in newState.providers {
toAdd[identifier] = provider
@ -411,7 +409,8 @@ public class EntityStateMachine<StateIdentifier: Hashable> {
for (identifier, _) in currentState.providers {
if let other = toAdd[identifier], let current = currentState.providers[identifier],
current.identifier == other.identifier {
current.identifier == other.identifier
{
toAdd[identifier] = nil
} else {
entity.remove(identifier)

View File

@ -14,6 +14,7 @@ extension CodingUserInfoKey {
}
// MARK: - encoding
extension FamilyMemberContainer: Encodable where R: FamilyEncoding {
func encode(to encoder: Encoder) throws {
let strategy = encoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy()
@ -50,11 +51,12 @@ extension Family where R: FamilyEncoding {
}
// MARK: - decoding
extension FamilyMemberContainer: Decodable where R: FamilyDecoding {
init(from decoder: Decoder) throws {
var familyContainer = try decoder.unkeyedContainer()
let strategy = decoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy()
self.components = try R.decode(componentsIn: &familyContainer, using: strategy)
components = try R.decode(componentsIn: &familyContainer, using: strategy)
}
}

View File

@ -53,7 +53,7 @@ public struct Family<R> where R: FamilyRequirementsManaging {
/// - Returns: The newly created member entity.
@discardableResult
public func createMember(@FamilyMemberBuilder<R> using builder: () -> R.Components) -> Entity {
self.createMember(with: builder())
createMember(with: builder())
}
}
@ -70,16 +70,17 @@ extension Family: Sequence {
}
}
extension Family: LazySequenceProtocol { }
extension Family: LazySequenceProtocol {}
// MARK: - components iterator
extension Family {
public struct ComponentsIterator: IteratorProtocol {
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>.ElementIterator
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
self.nexus = family.nexus
nexus = family.nexus
memberIdsIterator = family.memberIds.makeIterator()
}
@ -93,9 +94,11 @@ extension Family {
}
}
extension Family.ComponentsIterator: LazySequenceProtocol { }
extension Family.ComponentsIterator: LazySequenceProtocol {}
extension Family.ComponentsIterator: Sequence {}
// MARK: - entity iterator
extension Family {
@inlinable public var entities: EntityIterator {
EntityIterator(family: self)
@ -106,7 +109,7 @@ extension Family {
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
self.nexus = family.nexus
nexus = family.nexus
memberIdsIterator = family.memberIds.makeIterator()
}
@ -119,9 +122,11 @@ extension Family {
}
}
extension Family.EntityIterator: LazySequenceProtocol { }
extension Family.EntityIterator: LazySequenceProtocol {}
extension Family.EntityIterator: Sequence {}
// MARK: - entity component iterator
extension Family {
@inlinable public var entityAndComponents: EntityComponentIterator {
EntityComponentIterator(family: self)
@ -132,7 +137,7 @@ extension Family {
@usableFromInline unowned let nexus: Nexus
public init(family: Family<R>) {
self.nexus = family.nexus
nexus = family.nexus
memberIdsIterator = family.memberIds.makeIterator()
}
@ -145,9 +150,11 @@ extension Family {
}
}
extension Family.EntityComponentIterator: LazySequenceProtocol { }
extension Family.EntityComponentIterator: LazySequenceProtocol {}
extension Family.EntityComponentIterator: Sequence {}
// MARK: - member creation
extension Family {
/// Create a new entity with components required by this family.
///

View File

@ -5,6 +5,10 @@
// Created by Christian Treffs on 07.08.20.
//
@_functionBuilder
public enum FamilyMemberBuilderPreview<R> where R: FamilyRequirementsManaging { }
public typealias FamilyMemberBuilder<R> = FamilyMemberBuilderPreview<R> where R: FamilyRequirementsManaging
#if swift(<5.4)
@_functionBuilder
public enum FamilyMemberBuilder<R> where R: FamilyRequirementsManaging {}
#else
@resultBuilder
public enum FamilyMemberBuilder<R> where R: FamilyRequirementsManaging {}
#endif

View File

@ -19,7 +19,7 @@ public struct FamilyTraitSet {
self.requiresAll = requiresAll
self.excludesAll = excludesAll
self.setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll])
setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll])
}
@inlinable

View File

@ -6,8 +6,8 @@
//
#if canImport(Foundation)
import Foundation
import Foundation
extension JSONEncoder: TopLevelEncoder { }
extension JSONDecoder: TopLevelDecoder { }
extension JSONEncoder: TopLevelEncoder {}
extension JSONDecoder: TopLevelDecoder {}
#endif

View File

@ -1,6 +1,5 @@
// Generated using Sourcery 1.0.0 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.2.5 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable file_length
// swiftlint:disable function_parameter_count
// swiftlint:disable large_tuple
@ -23,13 +22,13 @@ public struct Requires1<Comp1>: FamilyRequirementsManaging where Comp1: Componen
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
return (comp1)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
return (entity, comp1)
}
@ -116,15 +115,15 @@ public struct Requires2<Comp1, Comp2>: FamilyRequirementsManaging where Comp1: C
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
return (comp1, comp2)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2)
}
@ -215,17 +214,17 @@ public struct Requires3<Comp1, Comp2, Comp3>: FamilyRequirementsManaging where C
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3)
}
@ -320,19 +319,19 @@ public struct Requires4<Comp1, Comp2, Comp3, Comp4>: FamilyRequirementsManaging
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3, Comp4) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3, comp4)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3, comp4)
}
@ -431,21 +430,21 @@ public struct Requires5<Comp1, Comp2, Comp3, Comp4, Comp5>: FamilyRequirementsMa
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3, Comp4, Comp5) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3, comp4, comp5)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3, comp4, comp5)
}
@ -548,23 +547,23 @@ public struct Requires6<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6>: FamilyRequire
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3, Comp4, Comp5, Comp6) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3, comp4, comp5, comp6)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3, comp4, comp5, comp6)
}
@ -671,25 +670,25 @@ public struct Requires7<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7>: Family
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp7: Comp7 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
let comp7: Comp7 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3, comp4, comp5, comp6, comp7)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp7: Comp7 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
let comp7: Comp7 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3, comp4, comp5, comp6, comp7)
}
@ -800,27 +799,27 @@ public struct Requires8<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8>:
}
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> (Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp7: Comp7 = nexus.get(unsafeComponentFor: entityId)
let comp8: Comp8 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
let comp7: Comp7 = nexus.get(unsafe: entityId)
let comp8: Comp8 = nexus.get(unsafe: entityId)
return (comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8)
}
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
let entity = Entity(nexus: nexus, id: entityId)
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
let comp4: Comp4 = nexus.get(unsafeComponentFor: entityId)
let comp5: Comp5 = nexus.get(unsafeComponentFor: entityId)
let comp6: Comp6 = nexus.get(unsafeComponentFor: entityId)
let comp7: Comp7 = nexus.get(unsafeComponentFor: entityId)
let comp8: Comp8 = nexus.get(unsafeComponentFor: entityId)
let comp1: Comp1 = nexus.get(unsafe: entityId)
let comp2: Comp2 = nexus.get(unsafe: entityId)
let comp3: Comp3 = nexus.get(unsafe: entityId)
let comp4: Comp4 = nexus.get(unsafe: entityId)
let comp5: Comp5 = nexus.get(unsafe: entityId)
let comp6: Comp6 = nexus.get(unsafe: entityId)
let comp7: Comp7 = nexus.get(unsafe: entityId)
let comp8: Comp8 = nexus.get(unsafe: entityId)
return (entity, comp1, comp2, comp3, comp4, comp5, comp6, comp7, comp8)
}

View File

@ -6,11 +6,11 @@
//
#if arch(x86_64) || arch(arm64) || arch(powerpc64) || arch(powerpc64le) || arch(s390x) // 64 bit
private let kFibA: UInt = 0x9e3779b97f4a7c15 // = 11400714819323198485 aka Fibonacci Hash a value for 2^64; calculate by: 2^64 / (golden ratio)
private let kFibA: UInt = 0x9E37_79B9_7F4A_7C15 // = 11400714819323198485 aka Fibonacci Hash a value for 2^64; calculate by: 2^64 / (golden ratio)
#elseif arch(i386) || arch(arm) || os(watchOS) || arch(wasm32) // 32 bit
private let kFibA: UInt = 0x9e3779b9 // = 2654435769 aka Fibonacci Hash a value for 2^32; calculate by: 2^32 / (golden ratio)
private let kFibA: UInt = 0x9E37_79B9 // = 2654435769 aka Fibonacci Hash a value for 2^32; calculate by: 2^32 / (golden ratio)
#else
#error("unsupported architecture")
#error("unsupported architecture")
#endif
/// entity id ^ component identifier hash
@ -20,8 +20,11 @@ public typealias EntityComponentHash = Int
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.
@ -45,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 {
@ -55,45 +60,49 @@ public func hash<H: Sequence>(combine hashValues: H) -> Int where H.Element: Has
}
// MARK: - entity component hash
extension EntityComponentHash {
internal static func compose(entityId: EntityIdentifier, componentTypeHash: ComponentTypeHash) -> EntityComponentHash {
static func compose(entityId: EntityIdentifier, componentTypeHash: ComponentTypeHash) -> EntityComponentHash {
let entityIdSwapped = UInt(entityId.id).byteSwapped // needs to be 64 bit
let componentTypeHashUInt = UInt(bitPattern: componentTypeHash)
let hashUInt: UInt = componentTypeHashUInt ^ entityIdSwapped
return Int(bitPattern: hashUInt)
}
internal static func decompose(_ hash: EntityComponentHash, with entityId: EntityIdentifier) -> ComponentTypeHash {
static func decompose(_ hash: EntityComponentHash, with entityId: EntityIdentifier) -> ComponentTypeHash {
let entityIdSwapped = UInt(entityId.id).byteSwapped
let entityIdSwappedInt = Int(bitPattern: entityIdSwapped)
return hash ^ entityIdSwappedInt
}
internal static func decompose(_ hash: EntityComponentHash, with componentTypeHash: ComponentTypeHash) -> EntityIdentifier {
static func decompose(_ hash: EntityComponentHash, with componentTypeHash: ComponentTypeHash) -> EntityIdentifier {
let entityId: Int = (hash ^ componentTypeHash).byteSwapped
return EntityIdentifier(UInt32(truncatingIfNeeded: entityId))
}
}
// 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()
while let char = iter.next() {
hash = 127 * (hash & 0xFFFFFFFFFFFFFF) &+ UInt64(char.value)
hash = 127 * (hash & 0xFF_FFFF_FFFF_FFFF) &+ UInt64(char.value)
}
return hash
}
/// *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>

View File

@ -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)
@ -92,6 +120,7 @@ public struct ManagedContiguousArray<Element> {
}
// MARK: - Equatable
extension ManagedContiguousArray: Equatable where Element: Equatable {
public static func == (lhs: ManagedContiguousArray<Element>, rhs: ManagedContiguousArray<Element>) -> Bool {
lhs.store == rhs.store
@ -99,4 +128,5 @@ extension ManagedContiguousArray: Equatable where Element: Equatable {
}
// MARK: - Codable
extension ManagedContiguousArray: Codable where Element: Codable { }
extension ManagedContiguousArray: Codable where Element: Codable {}

View File

@ -21,39 +21,49 @@ extension Nexus {
componentIdsByEntity[entityId]?.count ?? 0
}
public final func assign(component: Component, to entity: Entity) {
@discardableResult
public final func assign(component: Component, to entity: Entity) -> Bool {
let entityId: EntityIdentifier = entity.identifier
assign(component: component, entityId: entityId)
delegate?.nexusEvent(ComponentAdded(component: component.identifier, toEntity: entity.identifier))
return assign(component: component, entityId: entityId)
}
public final func assign<C>(component: C, to entity: Entity) where C: Component {
@discardableResult
public final func assign(component: some Component, to entity: Entity) -> Bool {
assign(component: component, to: entity)
}
@discardableResult
public final func assign(components: some Collection<Component>, to entity: Entity) -> Bool {
assign(components: components, to: entity.identifier)
}
@inlinable
public final func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
guard let uniformComponents = componentsByType[componentId] else {
public final func get(safe componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
guard let uniformComponents = componentsByType[componentId], uniformComponents.contains(entityId.index) else {
return nil
}
return uniformComponents.get(at: entityId.index)
}
@inlinable
public final func get(unsafeComponent componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component {
public final func get(unsafe componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component {
let uniformComponents = componentsByType[componentId].unsafelyUnwrapped
return uniformComponents.get(unsafeAt: entityId.index)
}
@inlinable
public final func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {
let componentId: ComponentIdentifier = C.identifier
return get(componentId: componentId, entityId: entityId)
public final func get<C>(safe componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> C? where C: Component {
get(safe: componentId, for: entityId) as? C
}
@inlinable
public final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component {
let component: Component = get(unsafeComponent: C.identifier, for: entityId)
public final func get<C>(safe entityId: EntityIdentifier) -> C? where C: Component {
get(safe: C.identifier, for: entityId)
}
@inlinable
public final func get<C>(unsafe entityId: EntityIdentifier) -> C where C: Component {
let component: Component = get(unsafe: C.identifier, for: entityId)
// components are guaranteed to be reference types so unsafeDowncast is applicable here
return unsafeDowncast(component, to: C.self)
}
@ -83,18 +93,10 @@ extension Nexus {
return false
}
var iter = allComponents.makeIterator()
var removedAll: Bool = true
var removedAll = true
while let component = iter.next() {
removedAll = removedAll && remove(component: component, from: entityId)
}
return removedAll
}
@inlinable
public final func get<C>(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> C? where C: Component {
guard let uniformComponents = componentsByType[componentId] else {
return nil
}
return uniformComponents.get(at: entityId.index) as? C
}
}

View File

@ -5,9 +5,13 @@
// Created by Christian Treffs on 30.07.20.
//
@_functionBuilder
public enum ComponentsBuilderPreview { }
public typealias ComponentsBuilder = ComponentsBuilderPreview
#if swift(<5.4)
@_functionBuilder
public enum ComponentsBuilder {}
#else
@resultBuilder
public enum ComponentsBuilder {}
#endif
extension ComponentsBuilder {
public static func buildBlock(_ components: Component...) -> [Component] {
@ -35,7 +39,7 @@ extension Nexus {
/// - Returns: The newly created entity with the provided component assigned.
@discardableResult
public func createEntity(@ComponentsBuilder using builder: () -> Component) -> Entity {
self.createEntity(with: builder())
createEntity(with: builder())
}
/// Create an entity assigning multiple components.
@ -51,7 +55,7 @@ extension Nexus {
/// - Returns: The newly created entity with the provided components assigned.
@discardableResult
public func createEntity(@ComponentsBuilder using builder: () -> [Component]) -> Entity {
self.createEntity(with: builder())
createEntity(with: builder())
}
/// Create multiple entities assigning one component each.
@ -68,7 +72,7 @@ extension Nexus {
/// - Returns: The newly created entities with the provided component assigned.
@discardableResult
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> Component) -> [Entity] {
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
(0 ..< count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
}
/// Create multiple entities assigning multiple components each.
@ -85,7 +89,7 @@ extension Nexus {
/// - builder: The component builder providing context.
/// - Returns: The newly created entities with the provided components assigned.
@discardableResult
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> [Component]) -> [Entity] {
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> [Component] = { _ in [] }) -> [Entity] {
(0 ..< count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
}
}

View File

@ -22,8 +22,8 @@ extension Nexus {
}
@discardableResult
public func createEntity<C>(with components: C) -> Entity where C: Collection, C.Element == Component {
let entity = self.createEntity()
public func createEntity(with components: some Collection<Component>) -> Entity {
let entity = createEntity()
assign(components: components, to: entity.identifier)
return entity
}
@ -50,7 +50,7 @@ extension Nexus {
@discardableResult
public func destroy(entity: Entity) -> Bool {
self.destroy(entityId: entity.identifier)
destroy(entityId: entity.identifier)
}
@discardableResult
@ -76,6 +76,7 @@ extension Nexus {
}
// MARK: - entities iterator
extension Nexus {
public struct EntitiesIterator: IteratorProtocol {
private var iterator: AnyIterator<Entity>
@ -96,4 +97,6 @@ extension Nexus {
}
}
}
extension Nexus.EntitiesIterator: Sequence { }
extension Nexus.EntitiesIterator: LazySequenceProtocol {}
extension Nexus.EntitiesIterator: Sequence {}

View File

@ -6,7 +6,9 @@
//
extension Nexus {
func assign<C>(components: C, to entityId: EntityIdentifier) where C: Collection, C.Element == Component {
@usableFromInline
@discardableResult
func assign(components: some Collection<Component>, to entityId: EntityIdentifier) -> Bool {
var iter = components.makeIterator()
while let component = iter.next() {
let componentId = component.identifier
@ -14,7 +16,7 @@ extension Nexus {
guard !has(componentId: componentId, entityId: entityId) else {
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
return
return false
}
// add component instances to uniform component stores
@ -26,16 +28,18 @@ extension Nexus {
// Update entity membership
update(familyMembership: entityId)
return true
}
func assign(component: Component, entityId: EntityIdentifier) {
@usableFromInline
func assign(component: Component, entityId: EntityIdentifier) -> Bool {
let componentId = component.identifier
// test if component is already assigned
guard !has(componentId: componentId, entityId: entityId) else {
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
return
return false
}
// add component instances to uniform component stores
@ -46,8 +50,10 @@ extension Nexus {
// Update entity membership
update(familyMembership: entityId)
return true
}
@usableFromInline
func insertComponentInstance(_ component: Component, _ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
if componentsByType[componentId] == nil {
componentsByType[componentId] = ManagedContiguousArray<Component>()
@ -55,10 +61,15 @@ extension Nexus {
componentsByType[componentId]?.insert(component, at: entityId.index)
}
@usableFromInline
func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
componentIdsByEntity[entityId]!.insert(componentId)
let (inserted, _) = componentIdsByEntity[entityId]!.insert(componentId)
if inserted {
delegate?.nexusEvent(ComponentAdded(component: componentId, toEntity: entityId))
}
}
@usableFromInline
func update(familyMembership entityId: EntityIdentifier) {
// FIXME: iterating all families is costly for many families
// FIXME: this could be parallelized
@ -92,8 +103,8 @@ extension Nexus {
return
}
let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits)
if !exists(entity: entityId) && isMember {
let isMember: Bool = isMember(entity: entityId, inFamilyWithTraits: traits)
if !exists(entity: entityId), isMember {
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
return
}

View File

@ -42,13 +42,14 @@ public final class Nexus {
codingStrategy: DefaultCodingStrategy())
}
internal init(componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
entityIdGenerator: EntityIdentifierGenerator,
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>],
codingStrategy: CodingStrategy) {
init(componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
entityIdGenerator: EntityIdentifierGenerator,
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>],
codingStrategy: CodingStrategy)
{
self.componentsByType = componentsByType
self.componentIdsByEntity = componentsByEntity
componentIdsByEntity = componentsByEntity
self.familyMembersByTraits = familyMembersByTraits
self.entityIdGenerator = entityIdGenerator
self.codingStrategy = codingStrategy
@ -66,6 +67,7 @@ public final class Nexus {
}
// MARK: - CustomDebugStringConvertible
extension Nexus: CustomDebugStringConvertible {
public var debugDescription: String {
"<Nexus entities:\(numEntities) components:\(numComponents) families:\(numFamilies)>"
@ -73,8 +75,9 @@ extension Nexus: CustomDebugStringConvertible {
}
// MARK: - default coding strategy
public struct DefaultCodingStrategy: CodingStrategy {
public init() { }
public init() {}
public func codingKey<C>(for componentType: C.Type) -> DynamicCodingKey where C: Component {
DynamicCodingKey(stringValue: "\(C.self)").unsafelyUnwrapped

View File

@ -1,5 +1,5 @@
//
// NexusEvents.swift
// NexusEvent.swift
// FirebladeECS
//
// Created by Christian Treffs on 08.10.17.
@ -34,13 +34,3 @@ public struct FamilyMemberRemoved: NexusEvent {
public let member: EntityIdentifier
public let from: FamilyTraitSet
}
public struct ChildAdded: NexusEvent {
public let parent: EntityIdentifier
public let child: EntityIdentifier
}
public struct ChildRemoved: NexusEvent {
public let parent: EntityIdentifier
public let child: EntityIdentifier
}

View File

@ -28,17 +28,17 @@ extension Single where A: SingleComponent {
// Since we guarantee that the component will always be present by managing the complete lifecycle of the entity
// and component assignment we may unsafelyUnwrap here.
// Since components will always be of reference type (class) we may use unsafeDowncast here for performance reasons.
nexus.get(unsafeComponentFor: entityId)
nexus.get(unsafe: entityId)
}
public var entity: Entity {
Entity(nexus: self.nexus, id: entityId)
Entity(nexus: nexus, id: entityId)
}
}
extension Nexus {
public func single<S>(_ component: S.Type) -> Single<S> where S: SingleComponent {
let family = self.family(requires: S.self)
let family = family(requires: S.self)
precondition(family.count <= 1, "Singleton count of \(S.self) must be 0 or 1: \(family.count)")
let entityId: EntityIdentifier
if family.isEmpty {

View File

@ -46,7 +46,7 @@ public struct Requires{{ idx }}<{{ CompParams }}>: FamilyRequirementsManaging wh
public static func components(nexus: Nexus, entityId: EntityIdentifier) -> ({{ CompParams }}) {
{% for comp in components %}
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafeComponentFor: entityId)
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafe: entityId)
{% endfor %}
return ({{ CompsLowercased }})
}
@ -54,7 +54,7 @@ public struct Requires{{ idx }}<{{ CompParams }}>: FamilyRequirementsManaging wh
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, {{ CompParams }}) {
let entity: Entity = Entity(nexus: nexus, id: entityId)
{% for comp in components %}
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafeComponentFor: entityId)
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafe: entityId)
{% endfor %}
return (entity, {{ CompsLowercased }})
}

View File

@ -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
@ -74,7 +74,7 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
guard let denseIndex = findIndex(at: key) else {
return nil
}
let entry = self.dense[denseIndex]
let entry = dense[denseIndex]
assert(entry.key == key, "entry.key and findIndex(at: key) must be equal!")
return entry.element
}
@ -99,7 +99,7 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
}
let removed = swapRemove(at: denseIndex)
if !dense.isEmpty && denseIndex < dense.count {
if !dense.isEmpty, denseIndex < dense.count {
let swappedElement = dense[denseIndex]
sparse[swappedElement.key] = denseIndex
}
@ -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 {
@ -208,12 +221,14 @@ extension UnorderedSparseSet where Key == Int {
}
// MARK: - Sequence
extension UnorderedSparseSet: Sequence {
public func makeIterator() -> ElementIterator {
ElementIterator(self)
}
// MARK: - UnorderedSparseSetIterator
public struct ElementIterator: IteratorProtocol {
var iterator: IndexingIterator<ContiguousArray<Storage.Entry>>
@ -227,14 +242,19 @@ extension UnorderedSparseSet: Sequence {
}
}
extension UnorderedSparseSet.ElementIterator: LazySequenceProtocol {}
extension UnorderedSparseSet.ElementIterator: Sequence {}
// MARK: - Equatable
extension UnorderedSparseSet.Storage.Entry: Equatable where Element: Equatable { }
extension UnorderedSparseSet.Storage.Entry: Equatable where Element: Equatable {}
extension UnorderedSparseSet.Storage: Equatable where Element: Equatable {
@usableFromInline
static func == (lhs: UnorderedSparseSet<Element, Key>.Storage, rhs: UnorderedSparseSet<Element, Key>.Storage) -> Bool {
lhs.dense == rhs.dense && lhs.sparse == rhs.sparse
}
}
extension UnorderedSparseSet: Equatable where Element: Equatable {
public static func == (lhs: UnorderedSparseSet<Element, Key>, rhs: UnorderedSparseSet<Element, Key>) -> Bool {
lhs.storage == rhs.storage
@ -242,6 +262,7 @@ extension UnorderedSparseSet: Equatable where Element: Equatable {
}
// MARK: - Codable
extension UnorderedSparseSet.Storage.Entry: Codable where Element: Codable { }
extension UnorderedSparseSet.Storage: Codable where Element: Codable { }
extension UnorderedSparseSet: Codable where Element: Codable { }
extension UnorderedSparseSet.Storage.Entry: Codable where Element: Codable {}
extension UnorderedSparseSet.Storage: Codable where Element: Codable {}
extension UnorderedSparseSet: Codable where Element: Codable {}

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 14.02.19.
//
#if os(macOS)
import FirebladeECS
import XCTest
@ -97,3 +98,6 @@ class HashingPerformanceTests: XCTestCase {
#endif
}
}
#else
#warning("Skipping HashingPerformanceTests")
#endif

View File

@ -5,6 +5,7 @@
// Created by Christian Treffs on 05.10.19.
//
#if os(macOS)
import FirebladeECS
import XCTest
@ -30,7 +31,7 @@ final class TypeIdentifierPerformanceTests: XCTestCase {
/// release: 1.034 sec
/// debug:
func testPerformanceHash() {
measure {
measure(options: .default) {
for _ in 0..<maxIterations {
_ = StringHashing.singer_djb2(String(describing: Color.self))
_ = StringHashing.singer_djb2(String(describing: EmptyComponent.self))
@ -91,3 +92,6 @@ final class TypeIdentifierPerformanceTests: XCTestCase {
}
}
}
#else
#warning("Skipping TypeIdentifierPerformanceTests tests")
#endif

View File

@ -1,70 +0,0 @@
#if !canImport(ObjectiveC)
import XCTest
extension ComponentIdentifierTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentIdentifierTests = [
("testMeasureComponentIdentifier", testMeasureComponentIdentifier),
("testMeasureStaticComponentIdentifier", testMeasureStaticComponentIdentifier)
]
}
extension HashingPerformanceTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__HashingPerformanceTests = [
("testMeasureBernsteinDjb2", testMeasureBernsteinDjb2),
("testMeasureCombineHash", testMeasureCombineHash),
("testMeasureSDBM", testMeasureSDBM),
("testMeasureSetOfSetHash", testMeasureSetOfSetHash),
("testMeasureSingerDjb2", testMeasureSingerDjb2),
("testMeasureSwiftHasher", testMeasureSwiftHasher)
]
}
extension TypeIdentifierPerformanceTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__TypeIdentifierPerformanceTests = [
("testPerformanceHash", testPerformanceHash),
("testPerformanceMirrorReflectingDescription", testPerformanceMirrorReflectingDescription),
("testPerformanceObjectIdentifier", testPerformanceObjectIdentifier),
("testPerformanceStringDescribing", testPerformanceStringDescribing),
("testPerformanceStringReflecting", testPerformanceStringReflecting)
]
}
extension TypedFamilyPerformanceTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__TypedFamilyPerformanceTests = [
("testMeasureTraitMatching", testMeasureTraitMatching),
("testPerformanceArray", testPerformanceArray),
("testPerformanceTypedFamilyEntities", testPerformanceTypedFamilyEntities),
("testPerformanceTypedFamilyEntityFiveComponents", testPerformanceTypedFamilyEntityFiveComponents),
("testPerformanceTypedFamilyEntityFourComponents", testPerformanceTypedFamilyEntityFourComponents),
("testPerformanceTypedFamilyEntityOneComponent", testPerformanceTypedFamilyEntityOneComponent),
("testPerformanceTypedFamilyEntityThreeComponents", testPerformanceTypedFamilyEntityThreeComponents),
("testPerformanceTypedFamilyEntityTwoComponents", testPerformanceTypedFamilyEntityTwoComponents),
("testPerformanceTypedFamilyFiveComponents", testPerformanceTypedFamilyFiveComponents),
("testPerformanceTypedFamilyFourComponents", testPerformanceTypedFamilyFourComponents),
("testPerformanceTypedFamilyOneComponent", testPerformanceTypedFamilyOneComponent),
("testPerformanceTypedFamilyThreeComponents", testPerformanceTypedFamilyThreeComponents),
("testPerformanceTypedFamilyTwoComponents", testPerformanceTypedFamilyTwoComponents)
]
}
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests),
testCase(TypeIdentifierPerformanceTests.__allTests__TypeIdentifierPerformanceTests),
testCase(TypedFamilyPerformanceTests.__allTests__TypedFamilyPerformanceTests)
]
}
#endif

View File

@ -10,17 +10,48 @@ import FirebladeECS
class EmptyComponent: Component {
}
class Name: Component {
final class Optionals: Component, DefaultInitializable {
var int: Int?
var float: Float?
var string: String?
convenience init() {
self.init(nil, nil, nil)
}
init(_ int: Int?, _ float: Float?, _ string: String?) {
self.int = int
self.float = float
self.string = string
}
}
extension Optionals: Equatable {
static func == (lhs: Optionals, rhs: Optionals) -> Bool {
lhs.int == rhs.int &&
lhs.float == rhs.float &&
lhs.string == rhs.string
}
}
final class Name: Component, DefaultInitializable {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "")
}
}
final class Position: Component {
final class Position: Component, DefaultInitializable {
var x: Int
var y: Int
convenience init() {
self.init(x: 0, y: 0)
}
init(x: Int, y: Int) {
self.x = x
self.y = y
@ -28,11 +59,16 @@ final class Position: Component {
}
extension Position: Codable { }
class Velocity: Component {
final class Velocity: Component, DefaultInitializable {
var a: Float
init(a: Float) {
self.a = a
}
convenience init() {
self.init(a: 0)
}
}
final class Party: Component {

View File

@ -34,7 +34,7 @@ class EntityTests: XCTestCase {
entity.assign(name, vel)
let expectedComponents: [Component] = [pos, name, vel]
let allComponents = entity.allComponents()
let allComponents = Array(entity.makeComponentsIterator())
XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 })
}
@ -101,11 +101,72 @@ class EntityTests: XCTestCase {
XCTAssertEqual(entity[\Name.name], "AnotherName")
entity[\Velocity.a] = 123
XCTAssertNil(entity[\Velocity.a])
XCTAssertEqual(entity[\Velocity.a], 123.0)
entity[Position.self]?.x = 1234
XCTAssertEqual(entity[Position.self]?.x, 1234)
XCTAssertNil(entity[Velocity.self]?.a)
XCTAssertEqual(entity[Velocity.self]?.a, 123.0)
// remove position component
entity[Position.self] = nil
XCTAssertNil(entity[Position.self])
entity[Position.self] = pos // assign position comp instance
XCTAssertTrue(entity[Position.self] === pos)
entity[Position.self] = pos // re-assign
XCTAssertTrue(entity[Position.self] === pos)
entity[Position.self] = nil // remove position component
XCTAssertNil(entity[Position.self])
let opts = Optionals(1, 2, "hello")
entity[Optionals.self] = opts
XCTAssertEqual(entity[Optionals.self], opts)
entity[\Optionals.float] = nil
XCTAssertEqual(entity[\Optionals.float], nil)
XCTAssertEqual(entity[\Optionals.int], 1)
XCTAssertEqual(entity[\Optionals.string], "hello")
entity[Optionals.self] = nil
XCTAssertNil(entity[Optionals.self])
entity[\Optionals.string] = "world"
XCTAssertEqual(entity[\Optionals.string], "world")
entity.assign(Comp1(12))
XCTAssertEqual(entity[\Comp1.value], 12)
}
func testComponentsIteration() {
let nexus = Nexus()
let entity = nexus.createEntity()
XCTAssertTrue(Array(entity.makeComponentsIterator()).isEmpty)
entity.assign(Position())
XCTAssertEqual(Array(entity.makeComponentsIterator()).count, 1)
}
func testEntityCreationIntrinsic() {
let nexus = Nexus()
let entity = nexus.createEntity()
let secondEntity = entity.createEntity()
XCTAssertNotEqual(secondEntity, entity)
let thirdEntity = secondEntity.createEntity()
XCTAssertNotEqual(secondEntity, thirdEntity)
XCTAssertNotEqual(entity, thirdEntity)
let entityWithComponents = entity.createEntity(with: Position(), Name())
XCTAssertTrue(entityWithComponents.has(Position.self))
XCTAssertTrue(entityWithComponents.has(Name.self))
XCTAssertEqual(nexus.numEntities, 4)
XCTAssertEqual(nexus.numComponents, 2)
}
func testEntityDescriptions() {
let nexus = Nexus()
let entt = nexus.createEntity()
XCTAssertFalse(entt.description.isEmpty)
XCTAssertFalse(entt.debugDescription.isEmpty)
}
}

View File

@ -1,6 +1,5 @@
// Generated using Sourcery 1.0.0 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.2.5 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
import FirebladeECS
import XCTest
@ -1476,3 +1475,4 @@ extension Comp8: Equatable {
}
}
extension Comp8: Codable { }

View File

@ -0,0 +1,232 @@
//
// NexusEventDelegateTests.swift
//
//
// Created by Christian Treffs on 25.11.20.
//
import FirebladeECS
import XCTest
final class NexusEventDelegateTests: XCTestCase {
lazy var nexus = Nexus()
fileprivate var delegateTester: DelegateTester!
override func setUp() {
super.setUp()
nexus = Nexus()
delegateTester = nil
}
func testEventEntityCreated() {
var entityCreatedEvents: [EntityCreated] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case let entityCreated as EntityCreated:
entityCreatedEvents.append(entityCreated)
default:
XCTFail("unexpected event \(event)")
return
}
})
nexus.delegate = delegateTester
XCTAssertEqual(entityCreatedEvents.count, 0)
nexus.createEntity()
XCTAssertEqual(entityCreatedEvents.count, 1)
nexus.createEntities(count: 100)
XCTAssertEqual(entityCreatedEvents.count, 101)
}
func testEventEntityDestroyed() {
var events: [EntityDestroyed] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case let event as EntityDestroyed:
events.append(event)
case _ as EntityCreated:
break
default:
XCTFail("unexpected event \(event)")
return
}
})
nexus.delegate = delegateTester
XCTAssertEqual(events.count, 0)
nexus.createEntities(count: 100)
XCTAssertEqual(events.count, 0)
for entitiy in nexus.makeEntitiesIterator() {
entitiy.destroy()
}
XCTAssertEqual(events.count, 100)
}
func testEventComponentAdded() {
var componentsAddedEvents: [ComponentAdded] = []
var entityCreatedEvents: [EntityCreated] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case let compAdded as ComponentAdded:
componentsAddedEvents.append(compAdded)
case let entityCreated as EntityCreated:
entityCreatedEvents.append(entityCreated)
default:
XCTFail("unexpected event \(event)")
return
}
})
nexus.delegate = delegateTester
XCTAssertEqual(componentsAddedEvents.count, 0)
XCTAssertEqual(entityCreatedEvents.count, 0)
let entity = nexus.createEntity()
entity.assign(MyComponent(name: "0", flag: true))
XCTAssertEqual(componentsAddedEvents.count, 1)
XCTAssertEqual(entityCreatedEvents.count, 1)
let entity2 = nexus.createEntity()
entity2.assign(MyComponent(name: "0", flag: true), YourComponent(number: 2))
XCTAssertEqual(componentsAddedEvents.count, 3)
XCTAssertEqual(entityCreatedEvents.count, 2)
}
func testEventComponentRemoved() {
var events: [ComponentRemoved] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case let event as ComponentRemoved:
events.append(event)
default:
XCTFail("unexpected event \(event)")
return
}
})
let entity = nexus.createEntity()
entity.assign(
MyComponent(name: "Hello", flag: false),
YourComponent(number: 3.14),
EmptyComponent()
)
XCTAssertEqual(entity.numComponents, 3)
XCTAssertEqual(events.count, 0)
nexus.delegate = delegateTester
entity.remove(MyComponent.self)
XCTAssertEqual(events.count, 1)
XCTAssertEqual(entity.numComponents, 2)
entity.remove(EmptyComponent.self)
XCTAssertEqual(events.count, 2)
XCTAssertEqual(entity.numComponents, 1)
entity.remove(YourComponent.self)
XCTAssertEqual(events.count, 3)
XCTAssertEqual(entity.numComponents, 0)
}
func testFamilyMemeberAdded() {
var eventsFamilyMemberRemoved: [FamilyMemberRemoved] = []
var eventsComponentRemoved: [ComponentRemoved] = []
var eventsEntityDestroyed: [EntityDestroyed] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case is FamilyMemberAdded,
is ComponentAdded,
is EntityCreated:
break
case let event as FamilyMemberRemoved:
eventsFamilyMemberRemoved.append(event)
case let event as ComponentRemoved:
eventsComponentRemoved.append(event)
case let event as EntityDestroyed:
eventsEntityDestroyed.append(event)
default:
XCTFail("unexpected event \(event)")
return
}
})
let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self)
nexus.delegate = delegateTester
family.createMember(with: (MyComponent(name: "Bla", flag: true), YourComponent(number: 85)))
family.createMember(with: (MyComponent(name: "Hello", flag: false), YourComponent(number: 05050)))
family.createMember(with: (MyComponent(name: "asdasd", flag: true), YourComponent(number: 9494949)))
XCTAssertEqual(eventsFamilyMemberRemoved.count, 0)
XCTAssertEqual(eventsComponentRemoved.count, 0)
XCTAssertEqual(family.count, 3)
XCTAssertEqual(eventsEntityDestroyed.count, 0)
XCTAssertTrue(family.destroyMembers())
XCTAssertEqual(eventsFamilyMemberRemoved.count, 3)
XCTAssertEqual(eventsComponentRemoved.count, 6)
XCTAssertEqual(family.count, 0)
XCTAssertEqual(eventsEntityDestroyed.count, 3)
}
func testFamilyMemberRemoved() {
var eventsMemberAdded: [FamilyMemberAdded] = []
var eventsComponentAdded: [ComponentAdded] = []
var eventsEntityCreated: [EntityCreated] = []
delegateTester = DelegateTester(onEvent: { event in
switch event {
case let event as FamilyMemberAdded:
eventsMemberAdded.append(event)
case let event as ComponentAdded:
eventsComponentAdded.append(event)
case let event as EntityCreated:
eventsEntityCreated.append(event)
default:
XCTFail("unexpected event \(event)")
return
}
})
let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self)
nexus.delegate = delegateTester
XCTAssertEqual(family.count, 0)
XCTAssertEqual(eventsMemberAdded.count, 0)
XCTAssertEqual(eventsComponentAdded.count, 0)
XCTAssertEqual(eventsEntityCreated.count, 0)
family.createMember(with: (MyComponent(name: "Bla", flag: true), YourComponent(number: 85)))
XCTAssertEqual(family.count, 1)
XCTAssertEqual(eventsMemberAdded.count, 1)
XCTAssertEqual(eventsComponentAdded.count, 2)
XCTAssertEqual(eventsEntityCreated.count, 1)
family.createMember(with: (MyComponent(name: "Hello", flag: false), YourComponent(number: 05050)))
XCTAssertEqual(family.count, 2)
XCTAssertEqual(eventsMemberAdded.count, 2)
XCTAssertEqual(eventsComponentAdded.count, 4)
XCTAssertEqual(eventsEntityCreated.count, 2)
}
}
fileprivate class DelegateTester: NexusEventDelegate {
var onEvent: (NexusEvent) -> ()
var onNonFatal: (String) -> ()
init(onEvent: @escaping (NexusEvent) -> Void = { _ in },
onNonFatal: @escaping (String) -> Void = { _ in }) {
self.onEvent = onEvent
self.onNonFatal = onNonFatal
}
func nexusEvent(_ event: NexusEvent) {
onEvent(event)
}
func nexusNonFatalError(_ message: String) {
onNonFatal(message)
}
}

View File

@ -1,436 +0,0 @@
#if !canImport(ObjectiveC)
import XCTest
extension ComponentIdentifierTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentIdentifierTests = [
("testMirrorAsStableIdentifier", testMirrorAsStableIdentifier),
("testStringDescribingAsStableIdentifier", testStringDescribingAsStableIdentifier)
]
}
extension ComponentInstanceProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentInstanceProviderTests = [
("testProviderReturnsTheInstance", testProviderReturnsTheInstance),
("testProvidersWithDifferentInstanceHaveDifferentIdentifier", testProvidersWithDifferentInstanceHaveDifferentIdentifier),
("testProvidersWithSameInstanceHaveSameIdentifier", testProvidersWithSameInstanceHaveSameIdentifier)
]
}
extension ComponentSingletonProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentSingletonProviderTests = [
("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType),
("testProviderReturnsSameInstanceEachTime", testProviderReturnsSameInstanceEachTime),
("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier),
("testProvidersWithSameTypeHaveDifferentIdentifier", testProvidersWithSameTypeHaveDifferentIdentifier)
]
}
extension ComponentTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentTests = [
("testComponentIdentifier", testComponentIdentifier)
]
}
extension ComponentTypeProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__ComponentTypeProviderTests = [
("testProviderReturnsAnInstanceOfType", testProviderReturnsAnInstanceOfType),
("testProviderReturnsNewInstanceEachTime", testProviderReturnsNewInstanceEachTime),
("testProvidersWithDifferentTypeHaveDifferentIdentifier", testProvidersWithDifferentTypeHaveDifferentIdentifier),
("testProvidersWithSameTypeHaveSameIdentifier", testProvidersWithSameTypeHaveSameIdentifier)
]
}
extension DynamicComponentProviderTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__DynamicComponentProviderTests = [
("testProviderReturnsTheInstance", testProviderReturnsTheInstance),
("testProvidersWithDifferentMethodsHaveDifferentIdentifier", testProvidersWithDifferentMethodsHaveDifferentIdentifier),
("testProvidersWithSameMethodHaveSameIdentifier", testProvidersWithSameMethodHaveSameIdentifier)
]
}
extension EntityCreationTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityCreationTests = [
("testBulkCreateEntitiesMultipleComponents", testBulkCreateEntitiesMultipleComponents),
("testBulkCreateEntitiesOneComponent", testBulkCreateEntitiesOneComponent),
("testCreateEntityMultipleComponents", testCreateEntityMultipleComponents),
("testCreateEntityOneComponent", testCreateEntityOneComponent)
]
}
extension EntityIdGenTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityIdGenTests = [
("testGenerateWithInitialIds", testGenerateWithInitialIds),
("testGeneratorDefaultInit", testGeneratorDefaultInit),
("testGeneratorMarkUnused", testGeneratorMarkUnused),
("testGeneratorWithDefaultEmptyCollection", testGeneratorWithDefaultEmptyCollection),
("testLinearIncrement", testLinearIncrement)
]
}
extension EntityStateMachineTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityStateMachineTests = [
("testCallChangeStateWithSameNameLeavesEntityComponentsIntact", testCallChangeStateWithSameNameLeavesEntityComponentsIntact),
("testCreateStateAddsState", testCreateStateAddsState),
("testCreateStateDoesNotChangeState", testCreateStateDoesNotChangeState),
("testEnterSecondStateAddsSecondStatesComponents", testEnterSecondStateAddsSecondStatesComponents),
("testEnterSecondStateDoesNotRemoveOverlappingComponents", testEnterSecondStateDoesNotRemoveOverlappingComponents),
("testEnterSecondStateRemovesDifferentComponentsOfSameType", testEnterSecondStateRemovesDifferentComponentsOfSameType),
("testEnterSecondStateRemovesFirstStatesComponents", testEnterSecondStateRemovesFirstStatesComponents),
("testEnterStateAddsStatesComponents", testEnterStateAddsStatesComponents),
("testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity", testGetsDeinitedWhileBeingStronglyReferencedByComponentAssignedToEntity)
]
}
extension EntityStateTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityStateTests = [
("testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType", testAddInstanceCreatesMappingAndSetsInstanceProviderForInstanceType),
("testAddMappingWithInstanceQualifierCreatesInstanceProvider", testAddMappingWithInstanceQualifierCreatesInstanceProvider),
("testAddMappingWithMethodQualifierCreatesDynamicProvider", testAddMappingWithMethodQualifierCreatesDynamicProvider),
("testAddMappingWithNoQualifierCreatesTypeProvider", testAddMappingWithNoQualifierCreatesTypeProvider),
("testAddMappingWithSingletonQualifierCreatesSingletonProvider", testAddMappingWithSingletonQualifierCreatesSingletonProvider),
("testAddMappingWithTypeQualifierCreatesTypeProvider", testAddMappingWithTypeQualifierCreatesTypeProvider),
("testAddMethodCreatesMappingAndSetsDynamicProviderForType", testAddMethodCreatesMappingAndSetsDynamicProviderForType),
("testAddProviderCreatesMappingAndSetsProvider", testAddProviderCreatesMappingAndSetsProvider),
("testAddSingletonCreatesMappingAndSetsSingletonProviderForType", testAddSingletonCreatesMappingAndSetsSingletonProviderForType),
("testAddTypeCreatesMappingAndSetsTypeProviderForType", testAddTypeCreatesMappingAndSetsTypeProviderForType),
("testHasProviderReturnsFalseForNotCreatedProvider", testHasProviderReturnsFalseForNotCreatedProvider),
("testHasProviderReturnsTrueForCreatedProvider", testHasProviderReturnsTrueForCreatedProvider),
("testProviderForTypeReturnsDynamicProvider", testProviderForTypeReturnsDynamicProvider),
("testProviderForTypeReturnsInstanceProvider", testProviderForTypeReturnsInstanceProvider),
("testProviderForTypeReturnsPassedProvider", testProviderForTypeReturnsPassedProvider),
("testProviderForTypeReturnsSingletonProvider", testProviderForTypeReturnsSingletonProvider),
("testProviderForTypeReturnsTypeProvider", testProviderForTypeReturnsTypeProvider),
("testProviderForTypeReturnsTypeProviderByDefault", testProviderForTypeReturnsTypeProviderByDefault)
]
}
extension EntityTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__EntityTests = [
("testAllComponentsOfEntity", testAllComponentsOfEntity),
("testEntityEquality", testEntityEquality),
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex),
("testEntityIdGenerator", testEntityIdGenerator),
("testEntitySubscripts", testEntitySubscripts),
("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity)
]
}
extension Family1Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family1Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family2Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family2Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family3Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family3Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family4Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family4Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family5Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family5Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family6Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family6Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family7Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family7Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension Family8Tests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Family8Tests = [
("testComponentIteration", testComponentIteration),
("testEntityComponentIteration", testEntityComponentIteration),
("testEntityIteration", testEntityIteration),
("testFamilyDecoding", testFamilyDecoding),
("testFamilyEncoding", testFamilyEncoding),
("testFamilyFailDecoding", testFamilyFailDecoding),
("testMemberCreation", testMemberCreation),
("testMemberCreationBuilder", testMemberCreationBuilder)
]
}
extension FamilyCodingTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__FamilyCodingTests = [
("testDecodeFamily4", testDecodeFamily4),
("testDecodeFamily5", testDecodeFamily5),
("testDecodingFamily1", testDecodingFamily1),
("testDecodingFamily2", testDecodingFamily2),
("testDecodingFamily3", testDecodingFamily3),
("testEncodeFamily2", testEncodeFamily2),
("testEncodeFamily3", testEncodeFamily3),
("testEncodeFamily4", testEncodeFamily4),
("testEncodeFamily5", testEncodeFamily5),
("testEncodingFamily1", testEncodingFamily1),
("testFailDecodingFamily", testFailDecodingFamily)
]
}
extension FamilyTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__FamilyTests = [
("testFamilyAbandoned", testFamilyAbandoned),
("testFamilyBulkDestroy", testFamilyBulkDestroy),
("testFamilyCreateMembers", testFamilyCreateMembers),
("testFamilyCreation", testFamilyCreation),
("testFamilyDestroyMembers", testFamilyDestroyMembers),
("testFamilyExchange", testFamilyExchange),
("testFamilyLateMember", testFamilyLateMember),
("testFamilyMemberBasicIteration", testFamilyMemberBasicIteration),
("testFamilyReuse", testFamilyReuse)
]
}
extension FamilyTraitsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__FamilyTraitsTests = [
("testTraitCommutativity", testTraitCommutativity),
("testTraitMatching", testTraitMatching)
]
}
extension HashingTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__HashingTests = [
("testCollisionsInCritialRange", testCollisionsInCritialRange),
("testStringHashes", testStringHashes)
]
}
extension NexusTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__NexusTests = [
("testComponentCreation", testComponentCreation),
("testComponentDeletion", testComponentDeletion),
("testComponentRetrieval", testComponentRetrieval),
("testComponentUniqueness", testComponentUniqueness),
("testEntityCreate", testEntityCreate),
("testEntityDestroy", testEntityDestroy),
("testEntityIteration", testEntityIteration)
]
}
extension SingleTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SingleTests = [
("testSingleCreation", testSingleCreation),
("testSingleCreationOnExistingFamilyMember", testSingleCreationOnExistingFamilyMember),
("testSingleEntityAndComponentCreation", testSingleEntityAndComponentCreation),
("testSingleReuse", testSingleReuse)
]
}
extension SparseSetTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SparseSetTests = [
("testAlternativeKey", testAlternativeKey),
("testEquality", testEquality),
("testSparseSetAdd", testSparseSetAdd),
("testSparseSetAddAndReplace", testSparseSetAddAndReplace),
("testSparseSetClear", testSparseSetClear),
("testSparseSetDoubleRemove", testSparseSetDoubleRemove),
("testSparseSetGet", testSparseSetGet),
("testSparseSetNonCongiuousData", testSparseSetNonCongiuousData),
("testSparseSetReduce", testSparseSetReduce),
("testSparseSetRemove", testSparseSetRemove),
("testSparseSetRemoveAndAdd", testSparseSetRemoveAndAdd),
("testSparseSetRemoveNonPresent", testSparseSetRemoveNonPresent),
("testStartEndIndex", testStartEndIndex),
("testSubscript", testSubscript)
]
}
extension StateComponentMappingTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__StateComponentMappingTests = [
("testAddAddsProviderToState", testAddAddsProviderToState),
("testAddReturnsSameMappingForDifferentComponentTypes", testAddReturnsSameMappingForDifferentComponentTypes),
("testAddReturnsSameMappingForSameComponentType", testAddReturnsSameMappingForSameComponentType)
]
}
extension SystemsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SystemsTests = [
("testSystemsUpdate", testSystemsUpdate)
]
}
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
testCase(ComponentInstanceProviderTests.__allTests__ComponentInstanceProviderTests),
testCase(ComponentSingletonProviderTests.__allTests__ComponentSingletonProviderTests),
testCase(ComponentTests.__allTests__ComponentTests),
testCase(ComponentTypeProviderTests.__allTests__ComponentTypeProviderTests),
testCase(DynamicComponentProviderTests.__allTests__DynamicComponentProviderTests),
testCase(EntityCreationTests.__allTests__EntityCreationTests),
testCase(EntityIdGenTests.__allTests__EntityIdGenTests),
testCase(EntityStateMachineTests.__allTests__EntityStateMachineTests),
testCase(EntityStateTests.__allTests__EntityStateTests),
testCase(EntityTests.__allTests__EntityTests),
testCase(Family1Tests.__allTests__Family1Tests),
testCase(Family2Tests.__allTests__Family2Tests),
testCase(Family3Tests.__allTests__Family3Tests),
testCase(Family4Tests.__allTests__Family4Tests),
testCase(Family5Tests.__allTests__Family5Tests),
testCase(Family6Tests.__allTests__Family6Tests),
testCase(Family7Tests.__allTests__Family7Tests),
testCase(Family8Tests.__allTests__Family8Tests),
testCase(FamilyCodingTests.__allTests__FamilyCodingTests),
testCase(FamilyTests.__allTests__FamilyTests),
testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests),
testCase(HashingTests.__allTests__HashingTests),
testCase(NexusTests.__allTests__NexusTests),
testCase(SingleTests.__allTests__SingleTests),
testCase(SparseSetTests.__allTests__SparseSetTests),
testCase(StateComponentMappingTests.__allTests__StateComponentMappingTests),
testCase(SystemsTests.__allTests__SystemsTests)
]
}
#endif

View File

@ -1,10 +0,0 @@
import XCTest
import FirebladeECSPerformanceTests
import FirebladeECSTests
var tests = [XCTestCaseEntry]()
tests += FirebladeECSPerformanceTests.__allTests()
tests += FirebladeECSTests.__allTests()
XCTMain(tests)

94
renovate.json Normal file
View File

@ -0,0 +1,94 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":combinePatchMinorReleases",
":rebaseStalePrs",
":renovatePrefix",
"customManagers:githubActionsVersions"
],
"prConcurrentLimit": 5,
"prHourlyLimit": 1,
"recreateWhen": "auto",
"prCreation": "not-pending",
"packageRules": [
{
"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"
]
}