Merge branch 'master' into feature/serialization
# Conflicts: # Tests/FirebladeECSTests/XCTestManifests.swift
This commit is contained in:
commit
e02bf4f8b8
|
|
@ -1,5 +1,6 @@
|
|||
ignore:
|
||||
- "Tests/"
|
||||
- "Benchmarks/"
|
||||
|
||||
comment:
|
||||
layout: header, changes, diff
|
||||
layout: header, changes, diff
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
github: [ctreffs]
|
||||
custom: ['https://www.paypal.com/donate?hosted_button_id=GCG3K54SKRALQ']
|
||||
|
|
@ -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.*
|
||||
|
|
@ -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.*
|
||||
|
|
@ -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!
|
||||
|
|
@ -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).
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
name: Linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
linux-test-build-release:
|
||||
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.4.3
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
name: macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
CODECOV_XCODE_VERSION: "15.4" # Xcode version used to generate code coverage
|
||||
|
||||
jobs:
|
||||
macos-test-build-release-xcode:
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ["14.3.1", "15.4", "16.0"]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Select Xcode ${{ matrix.xcode }}
|
||||
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
|
||||
- name: Test
|
||||
run: swift test -c release --parallel --xunit-output .build/xUnit-output.xml --enable-code-coverage
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: test-artifacts-${{ matrix.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.xcode == env.CODECOV_XCODE_VERSION
|
||||
run: xcrun llvm-cov export -format="lcov" .build/**/*PackageTests.xctest/Contents/MacOS/*PackageTests -instr-profile .build/**/codecov/default.profdata > coverage.lcov
|
||||
|
||||
- name: Upload code coverage report
|
||||
if: env.CODECOV_TOKEN != '' && matrix.xcode == env.CODECOV_XCODE_VERSION
|
||||
uses: codecov/codecov-action@v4.6.0
|
||||
with:
|
||||
token: ${{ env.CODECOV_TOKEN }}
|
||||
file: coverage.lcov
|
||||
fail_ci_if_error: true
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
name: WASM
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
wasm-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build
|
||||
uses: swiftwasm/swiftwasm-action@v5.9
|
||||
with:
|
||||
shell-action: swift build --triple wasm32-unknown-wasi
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
name: Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
windows-test-build-release:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup
|
||||
uses: compnerd/gha-setup-swift@v0.2.3
|
||||
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.4.3
|
||||
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
|
||||
|
|
@ -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@main
|
||||
with:
|
||||
shell-action: swift build --triple wasm32-unknown-wasi
|
||||
|
|
@ -18,8 +18,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Generate documentation
|
||||
uses: SwiftDocOrg/swift-doc@master
|
||||
- name: Generate Swift Doc Documentation
|
||||
uses: SwiftDocOrg/swift-doc@1.0.0-rc.1
|
||||
with:
|
||||
inputs: "Sources/FirebladeECS"
|
||||
output: "Documentation"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1 +1 @@
|
|||
5.1.3
|
||||
5.8
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// Base.swift
|
||||
// FirebladeECSTests
|
||||
//
|
||||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
import FirebladeECS
|
||||
|
||||
class EmptyComponent: Component {}
|
||||
|
||||
class Name: Component {
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
class Position: Component {
|
||||
var x: Int
|
||||
var y: Int
|
||||
init(x: Int, y: Int) {
|
||||
self.x = x
|
||||
self.y = y
|
||||
}
|
||||
}
|
||||
|
||||
class Velocity: Component {
|
||||
var a: Float
|
||||
init(a: Float) {
|
||||
self.a = a
|
||||
}
|
||||
}
|
||||
|
||||
class Party: Component {
|
||||
var partying: Bool
|
||||
init(partying: Bool) {
|
||||
self.partying = partying
|
||||
}
|
||||
}
|
||||
|
||||
class Color: Component {
|
||||
var r: UInt8 = 0
|
||||
var g: UInt8 = 0
|
||||
var b: UInt8 = 0
|
||||
}
|
||||
|
||||
class ExampleSystem {
|
||||
private let family: Family2<Position, Velocity>
|
||||
|
||||
init(nexus: Nexus) {
|
||||
family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self)
|
||||
}
|
||||
|
||||
func update(deltaT _: Double) {
|
||||
for (position, velocity) in family {
|
||||
position.x *= 2
|
||||
velocity.a *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SingleGameState: SingleComponent {
|
||||
var shouldQuit: Bool = false
|
||||
var playerHealth: Int = 67
|
||||
}
|
||||
|
||||
func setUpNexus() -> Nexus {
|
||||
let numEntities = 10000
|
||||
let nexus = Nexus()
|
||||
|
||||
for i in 0 ..< numEntities {
|
||||
nexus.createEntity().assign(Position(x: 1 + i, y: 2 + i),
|
||||
Name(name: "myName\(i)"),
|
||||
Velocity(a: 3.14),
|
||||
EmptyComponent(),
|
||||
Color())
|
||||
}
|
||||
|
||||
precondition(nexus.numEntities == numEntities)
|
||||
// precondition(nexus.numFamilies == 1)
|
||||
precondition(nexus.numComponents == numEntities * 5)
|
||||
|
||||
return nexus
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
// swiftformat:disable preferForLoop
|
||||
import Benchmark
|
||||
import FirebladeECS
|
||||
|
||||
// derived from FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift in the parent project
|
||||
|
||||
let benchmarks = {
|
||||
Benchmark("TraitMatching") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let a = nexus.createEntity()
|
||||
a.assign(Position(x: 1, y: 2))
|
||||
a.assign(Name(name: "myName"))
|
||||
a.assign(Velocity(a: 3.14))
|
||||
a.assign(EmptyComponent())
|
||||
|
||||
let isMatch = nexus.family(requiresAll: Position.self, Velocity.self,
|
||||
excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
isMatch.canBecomeMember(a)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntities") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entities
|
||||
.forEach { (entity: Entity) in
|
||||
_ = entity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyOneComponent") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position) in
|
||||
_ = position
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityOneComponent") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requires: Position.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position) in
|
||||
_ = entity
|
||||
_ = position
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyTwoComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Benchmark("TypedFamilyEntityTwoComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyThreeComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Benchmark("TypedFamilyEntityThreeComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyFourComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name, color: Color) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityFourComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self)
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name, color: Color) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyFiveComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(
|
||||
family
|
||||
.forEach { (position: Position, velocity: Velocity, name: Name, color: Color, empty: EmptyComponent) in
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
_ = empty
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark("TypedFamilyEntityFiveComponents") { benchmark in
|
||||
let nexus = setUpNexus()
|
||||
let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)
|
||||
|
||||
for _ in benchmark.scaledIterations {
|
||||
blackHole(family
|
||||
.entityAndComponents
|
||||
.forEach { (entity: Entity, position: Position, velocity: Velocity, name: Name, color: Color, empty: EmptyComponent) in
|
||||
_ = entity
|
||||
_ = position
|
||||
_ = velocity
|
||||
_ = name
|
||||
_ = color
|
||||
_ = empty
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// swift-tools-version: 5.8
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ECSBenchmarks",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v13)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../"),
|
||||
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.27.3"))
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
name: "ECSBenchmark",
|
||||
dependencies: [
|
||||
.product(name: "FirebladeECS", package: "ecs"),
|
||||
.product(name: "Benchmark", package: "package-benchmark")
|
||||
],
|
||||
path: "Benchmarks/ECSBenchmark",
|
||||
plugins: [
|
||||
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Benchmarks for FirebladeECS
|
||||
|
||||
Originally seeded by replicating performance tests into a new form leveraging [package-benchmark](https://swiftpackageindex.com/ordo-one/package-benchmark/) [Documentation](https://swiftpackageindex.com/ordo-one/package-benchmark/main/documentation/benchmark).
|
||||
|
||||
To run all the available benchmarks:
|
||||
|
||||
swift package benchmark --format markdown
|
||||
|
||||
For more help on the package-benchmark SwiftPM plugin:
|
||||
|
||||
swift package benchmark help
|
||||
|
||||
Creating a local baseline:
|
||||
|
||||
swift package --allow-writing-to-package-directory benchmark baseline update dev
|
||||
swift package benchmark baseline list
|
||||
|
||||
Comparing to a the baseline 'alpha'
|
||||
|
||||
swift package benchmark baseline compare dev
|
||||
|
||||
For more details on creating and comparing baselines, read [Creating and Comparing Benchmark Baselines](https://swiftpackageindex.com/ordo-one/package-benchmark/main/documentation/benchmark/creatingandcomparingbaselines).
|
||||
|
|
@ -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
|
||||
|
|
@ -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/
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
108
Makefile
108
Makefile
|
|
@ -1,79 +1,61 @@
|
|||
# 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
|
||||
|
||||
brewSetup:
|
||||
which -s brew || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
|
||||
|
||||
brewUpdate: brewSetup
|
||||
brew update
|
||||
|
||||
setupEnvironment: brewInstallDeps
|
||||
open Package.swift
|
||||
|
||||
# lines of code
|
||||
loc: clean
|
||||
find . -name "*.swift" -print0 | xargs -0 wc -l
|
||||
# Clean sourcery cache
|
||||
.PHONY: clean-sourcery
|
||||
clean-sourcery:
|
||||
rm -rdf ${HOME}/Library/Caches/Sourcery
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
realm/SwiftLint@0.57.0
|
||||
nicklockwood/SwiftFormat@0.54.6
|
||||
krzysztofzablocki/Sourcery@2.2.5
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// swift-tools-version:5.1
|
||||
// swift-tools-version:5.8
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
|
|
@ -8,9 +8,11 @@ let package = Package(
|
|||
targets: ["FirebladeECS"])
|
||||
],
|
||||
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"])
|
||||
],
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -1,11 +1,13 @@
|
|||
# Fireblade ECS (Entity-Component System)
|
||||
[](LICENSE)
|
||||
[](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
|
||||
[](https://codecov.io/gh/fireblade-engine/ecs)
|
||||
[](https://github.com/fireblade-engine/ecs/actions/workflows/ci-macos.yml)
|
||||
[](https://github.com/fireblade-engine/ecs/actions/workflows/ci-linux.yml)
|
||||
[](https://github.com/fireblade-engine/ecs/actions/workflows/ci-windows.yml)
|
||||
[](https://github.com/fireblade-engine/ecs/actions/workflows/ci-wasm.yml)
|
||||
[](https://github.com/fireblade-engine/ecs/wiki)
|
||||
[](https://codecov.io/gh/fireblade-engine/ecs)
|
||||
[](https://swiftpackageindex.com/fireblade-engine/ecs)
|
||||
[](https://swiftpackageindex.com/fireblade-engine/ecs)
|
||||
[](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.3")
|
||||
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.5")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -34,5 +34,5 @@ extension ComponentIdentifier {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComponentIdentifier: Equatable { }
|
||||
extension ComponentIdentifier: Hashable { }
|
||||
extension ComponentIdentifier: Equatable {}
|
||||
extension ComponentIdentifier: Hashable {}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -38,13 +38,13 @@ public struct Entity {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
public func createEntity<C>(with components: C) -> Entity where C: Collection, C.Element == Component {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -78,13 +78,13 @@ 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 {
|
||||
public func assign(_ component: some Component) -> Entity {
|
||||
assign(component)
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func assign<C>(_ components: C) -> Entity where C: Collection, C.Element == Component {
|
||||
public func assign(_ components: some Collection<Component>) -> Entity {
|
||||
nexus.assign(components: components, to: self)
|
||||
return self
|
||||
}
|
||||
|
|
@ -92,14 +92,14 @@ public struct Entity {
|
|||
/// 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)
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ public struct Entity {
|
|||
|
||||
extension Entity {
|
||||
public struct ComponentsIterator: IteratorProtocol {
|
||||
private var iterator: IndexingIterator<([Component])>?
|
||||
private var iterator: IndexingIterator<[Component]>?
|
||||
|
||||
@usableFromInline
|
||||
init(nexus: Nexus, entityIdentifier: EntityIdentifier) {
|
||||
|
|
@ -144,8 +144,9 @@ extension Entity {
|
|||
}
|
||||
}
|
||||
}
|
||||
extension Entity.ComponentsIterator: LazySequenceProtocol { }
|
||||
extension Entity.ComponentsIterator: Sequence { }
|
||||
|
||||
extension Entity.ComponentsIterator: LazySequenceProtocol {}
|
||||
extension Entity.ComponentsIterator: Sequence {}
|
||||
|
||||
extension Entity: Equatable {
|
||||
public static func == (lhs: Entity, rhs: Entity) -> Bool {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,10 +94,11 @@ extension Family {
|
|||
}
|
||||
}
|
||||
|
||||
extension Family.ComponentsIterator: LazySequenceProtocol { }
|
||||
extension Family.ComponentsIterator: Sequence { }
|
||||
extension Family.ComponentsIterator: LazySequenceProtocol {}
|
||||
extension Family.ComponentsIterator: Sequence {}
|
||||
|
||||
// MARK: - entity iterator
|
||||
|
||||
extension Family {
|
||||
@inlinable public var entities: EntityIterator {
|
||||
EntityIterator(family: self)
|
||||
|
|
@ -107,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()
|
||||
}
|
||||
|
||||
|
|
@ -120,10 +122,11 @@ extension Family {
|
|||
}
|
||||
}
|
||||
|
||||
extension Family.EntityIterator: LazySequenceProtocol { }
|
||||
extension Family.EntityIterator: Sequence { }
|
||||
extension Family.EntityIterator: LazySequenceProtocol {}
|
||||
extension Family.EntityIterator: Sequence {}
|
||||
|
||||
// MARK: - entity component iterator
|
||||
|
||||
extension Family {
|
||||
@inlinable public var entityAndComponents: EntityComponentIterator {
|
||||
EntityComponentIterator(family: self)
|
||||
|
|
@ -134,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()
|
||||
}
|
||||
|
||||
|
|
@ -147,10 +150,11 @@ extension Family {
|
|||
}
|
||||
}
|
||||
|
||||
extension Family.EntityComponentIterator: LazySequenceProtocol { }
|
||||
extension Family.EntityComponentIterator: Sequence { }
|
||||
extension Family.EntityComponentIterator: LazySequenceProtocol {}
|
||||
extension Family.EntityComponentIterator: Sequence {}
|
||||
|
||||
// MARK: - member creation
|
||||
|
||||
extension Family {
|
||||
/// Create a new entity with components required by this family.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
//
|
||||
|
||||
#if canImport(Foundation)
|
||||
import Foundation
|
||||
import Foundation
|
||||
|
||||
extension JSONEncoder: TopLevelEncoder { }
|
||||
extension JSONDecoder: TopLevelDecoder { }
|
||||
extension JSONEncoder: TopLevelEncoder {}
|
||||
extension JSONDecoder: TopLevelDecoder {}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,6 +20,7 @@ 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.
|
||||
///
|
||||
|
|
@ -55,27 +56,29 @@ 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>
|
||||
public enum StringHashing {
|
||||
/// *Waren Singer djb2*
|
||||
|
|
@ -85,7 +88,7 @@ public enum StringHashing {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,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 +100,5 @@ extension ManagedContiguousArray: Equatable where Element: Equatable {
|
|||
}
|
||||
|
||||
// MARK: - Codable
|
||||
extension ManagedContiguousArray: Codable where Element: Codable { }
|
||||
|
||||
extension ManagedContiguousArray: Codable where Element: Codable {}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ extension Nexus {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
public final func assign<C>(component: C, to entity: Entity) -> Bool where C: Component {
|
||||
public final func assign(component: some Component, to entity: Entity) -> Bool {
|
||||
assign(component: component, to: entity)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public final func assign<C>(components: C, to entity: Entity) -> Bool where C: Collection, C.Element == Component {
|
||||
public final func assign(components: some Collection<Component>, to entity: Entity) -> Bool {
|
||||
assign(components: components, to: entity.identifier)
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -86,6 +90,6 @@ extension Nexus {
|
|||
/// - Returns: The newly created entities with the provided components assigned.
|
||||
@discardableResult
|
||||
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> [Component] = { _ in [] }) -> [Entity] {
|
||||
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
|
||||
(0 ..< count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,5 +97,6 @@ extension Nexus {
|
|||
}
|
||||
}
|
||||
}
|
||||
extension Nexus.EntitiesIterator: LazySequenceProtocol { }
|
||||
extension Nexus.EntitiesIterator: Sequence { }
|
||||
|
||||
extension Nexus.EntitiesIterator: LazySequenceProtocol {}
|
||||
extension Nexus.EntitiesIterator: Sequence {}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
extension Nexus {
|
||||
@usableFromInline
|
||||
@discardableResult
|
||||
func assign<C>(components: C, to entityId: EntityIdentifier) -> Bool where C: Collection, C.Element == Component {
|
||||
func assign(components: some Collection<Component>, to entityId: EntityIdentifier) -> Bool {
|
||||
var iter = components.makeIterator()
|
||||
while let component = iter.next() {
|
||||
let componentId = component.identifier
|
||||
|
|
@ -103,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,13 +47,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
|
||||
|
|
@ -71,6 +72,7 @@ public final class Nexus {
|
|||
}
|
||||
|
||||
// MARK: - CustomDebugStringConvertible
|
||||
|
||||
extension Nexus: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
"<Nexus entities:\(numEntities) components:\(numComponents) families:\(numFamilies)>"
|
||||
|
|
@ -78,8 +80,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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// NexusEvents.swift
|
||||
// NexusEvent.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 08.10.17.
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ extension Single where A: SingleComponent {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -208,12 +208,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>>
|
||||
|
||||
|
|
@ -226,17 +228,20 @@ extension UnorderedSparseSet: Sequence {
|
|||
}
|
||||
}
|
||||
}
|
||||
extension UnorderedSparseSet.ElementIterator: LazySequenceProtocol { }
|
||||
extension UnorderedSparseSet.ElementIterator: 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
|
||||
|
|
@ -244,6 +249,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 {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import XCTest
|
||||
|
||||
import FirebladeECSPerformanceTests
|
||||
import FirebladeECSTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += FirebladeECSPerformanceTests.__allTests()
|
||||
tests += FirebladeECSTests.__allTests()
|
||||
|
||||
XCTMain(tests)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Accumulate non-major updates into one pull request",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchCurrentVersion": ">=1",
|
||||
"groupName": "all non-major dependencies",
|
||||
"groupSlug": "all-minor-patch"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue