├── .gitignore
├── .swift-format.json
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ ├── LarkExample.xcscheme
│ ├── swift-lark-Package.xcscheme
│ └── swift-lark.xcscheme
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── CSDL2
│ ├── apple_iOS.h
│ ├── apple_macos.h
│ ├── linux.h
│ ├── module.modulemap
│ └── shims.h
├── CSDL2Image
│ ├── module.modulemap
│ └── shims.h
├── Lark
│ ├── AssetManagement
│ │ ├── AssetID.swift
│ │ ├── AssetStore.swift
│ │ └── ResourcePath.swift
│ ├── ECS
│ │ ├── Component.swift
│ │ ├── ComponentProxy.swift
│ │ ├── ComponentSignature.swift
│ │ ├── Components
│ │ │ ├── RigidBodyComponent.swift
│ │ │ ├── SpriteComponent.swift
│ │ │ └── TransformComponent.swift
│ │ ├── Entity.swift
│ │ ├── Internal
│ │ │ ├── ComponentPool.swift
│ │ │ ├── ComponentPoolManager.swift
│ │ │ ├── EntityLifetimeManager.swift
│ │ │ ├── SystemComponentProxy.swift
│ │ │ └── SystemsManager.swift
│ │ ├── Registry.swift
│ │ ├── System.swift
│ │ └── Systems
│ │ │ ├── MovementSystem.swift
│ │ │ └── RenderingSystem.swift
│ ├── Engine
│ │ ├── Engine.swift
│ │ ├── EngineInitOptions.swift
│ │ ├── Game.swift
│ │ ├── LarkError.swift
│ │ ├── SDLError.swift
│ │ └── Scene.swift
│ ├── Environment
│ │ ├── Environment.swift
│ │ ├── EnvironmentKey.swift
│ │ ├── EnvironmentValues.swift
│ │ ├── Logger.swift
│ │ └── UpdateClock.swift
│ ├── Events
│ │ ├── Event+SDL.swift
│ │ ├── Event.swift
│ │ ├── EventKinds.swift
│ │ └── Events.swift
│ ├── Extensions
│ │ ├── Duration+.swift
│ │ ├── Logger+.swift
│ │ ├── Numeric+Extensions.swift
│ │ └── Task+CancellableValue.swift
│ ├── FileManagement
│ │ └── File.swift
│ ├── Geometry
│ │ ├── Angle.swift
│ │ ├── Rect.swift
│ │ ├── Size2.swift
│ │ └── Vector2.swift
│ ├── Input
│ │ ├── KeyCode.swift
│ │ ├── KeyMod.swift
│ │ └── ScanCode.swift
│ ├── Rendering
│ │ ├── Color.swift
│ │ ├── DisplayMode.swift
│ │ ├── Files.swift
│ │ ├── PixelFormat.swift
│ │ ├── Renderer.swift
│ │ ├── Surface.swift
│ │ ├── Texture.swift
│ │ └── Window.swift
│ ├── Time
│ │ ├── GetTicks.swift
│ │ ├── LarkClock.swift
│ │ └── LarkDuration.swift
│ └── Utilities
│ │ ├── ActorIsolated.swift
│ │ ├── Bimap.swift
│ │ ├── Configurable.swift
│ │ ├── EmptyInitializable.swift
│ │ ├── Endianness.swift
│ │ ├── IncrementingNumberGenerator.swift
│ │ ├── LockedIncrementer.swift
│ │ ├── Optional+OrThrow.swift
│ │ ├── Proxy.swift
│ │ ├── SDLPointers.swift
│ │ └── UncheckedSendable.swift
├── LarkExample
│ ├── Entities.swift
│ ├── Run.swift
│ ├── Scenes
│ │ ├── Jungle.swift
│ │ └── TestScene.swift
│ └── assets
│ │ ├── fonts
│ │ ├── arial.ttf
│ │ └── charriot.ttf
│ │ ├── images
│ │ ├── bullet.png
│ │ ├── chopper-spritesheet.png
│ │ ├── chopper.png
│ │ ├── landing-base.png
│ │ ├── radar.png
│ │ ├── takeoff-base.png
│ │ ├── tank-panther-down.png
│ │ ├── tank-panther-left.png
│ │ ├── tank-panther-right.png
│ │ ├── tank-panther-up.png
│ │ ├── tank-tiger-down.png
│ │ ├── tank-tiger-left.png
│ │ ├── tank-tiger-right.png
│ │ ├── tank-tiger-up.png
│ │ ├── tree.png
│ │ ├── truck-ford-down.png
│ │ ├── truck-ford-killed.png
│ │ ├── truck-ford-left.png
│ │ ├── truck-ford-right.png
│ │ └── truck-ford-up.png
│ │ ├── sounds
│ │ └── helicopter.wav
│ │ └── tilemaps
│ │ ├── jungle.map
│ │ └── jungle.png
├── SDL2
│ └── Export.swift
└── SDL2Image
│ └── SDL2Image.swift
└── Tests
└── LarkTests
└── ColorTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swift-format.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileScopedDeclarationPrivacy" : {
3 | "accessLevel" : "private"
4 | },
5 | "indentation" : {
6 | "spaces" : 2
7 | },
8 | "indentConditionalCompilationBlocks" : true,
9 | "indentSwitchCaseLabels" : false,
10 | "lineBreakAroundMultilineExpressionChainComponents" : false,
11 | "lineBreakBeforeControlFlowKeywords" : false,
12 | "lineBreakBeforeEachArgument" : false,
13 | "lineBreakBeforeEachGenericRequirement" : false,
14 | "lineLength" : 100,
15 | "maximumBlankLines" : 1,
16 | "prioritizeKeepingFunctionOutputTogether" : false,
17 | "respectsExistingLineBreaks" : true,
18 | "rules" : {
19 | "AllPublicDeclarationsHaveDocumentation" : false,
20 | "AlwaysUseLowerCamelCase" : true,
21 | "AmbiguousTrailingClosureOverload" : true,
22 | "BeginDocumentationCommentWithOneLineSummary" : true,
23 | "DoNotUseSemicolons" : true,
24 | "DontRepeatTypeInStaticProperties" : false,
25 | "FileScopedDeclarationPrivacy" : true,
26 | "FullyIndirectEnum" : true,
27 | "GroupNumericLiterals" : true,
28 | "IdentifiersMustBeASCII" : true,
29 | "NeverForceUnwrap" : false,
30 | "NeverUseForceTry" : true,
31 | "NeverUseImplicitlyUnwrappedOptionals" : true,
32 | "NoAccessLevelOnExtensionDeclaration" : true,
33 | "NoAssignmentInExpressions" : true,
34 | "NoBlockComments" : true,
35 | "NoCasesWithOnlyFallthrough" : true,
36 | "NoEmptyTrailingClosureParentheses" : false,
37 | "NoLabelsInCasePatterns" : true,
38 | "NoLeadingUnderscores" : false,
39 | "NoParensAroundConditions" : true,
40 | "NoVoidReturnOnFunctionSignature" : true,
41 | "OneCasePerLine" : true,
42 | "OneVariableDeclarationPerLine" : true,
43 | "OnlyOneTrailingClosureArgument" : true,
44 | "OrderedImports" : true,
45 | "ReturnVoidInsteadOfEmptyTuple" : true,
46 | "UseEarlyExits" : true,
47 | "UseLetInEveryBoundCaseVariable" : true,
48 | "UseShorthandTypeNames" : true,
49 | "UseSingleLinePropertyGetter" : true,
50 | "UseSynthesizedInitializer" : true,
51 | "UseTripleSlashForDocumentationComments" : true,
52 | "UseWhereClausesInForLoops" : true,
53 | "ValidateDocumentationComments" : true
54 | },
55 | "spacesAroundRangeFormationOperators" : false,
56 | "tabWidth" : 2,
57 | "version" : 1
58 | }
59 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/LarkExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
17 |
18 |
24 |
30 |
31 |
32 |
38 |
44 |
45 |
46 |
47 |
48 |
54 |
55 |
57 |
63 |
64 |
65 |
66 |
67 |
80 |
82 |
88 |
89 |
90 |
91 |
97 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/swift-lark-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
57 |
63 |
64 |
65 |
71 |
77 |
78 |
79 |
85 |
91 |
92 |
93 |
99 |
105 |
106 |
107 |
113 |
119 |
120 |
121 |
127 |
133 |
134 |
135 |
136 |
137 |
143 |
144 |
146 |
152 |
153 |
154 |
155 |
156 |
166 |
167 |
173 |
174 |
175 |
176 |
182 |
183 |
189 |
190 |
191 |
192 |
194 |
195 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/swift-lark.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
43 |
49 |
50 |
56 |
57 |
58 |
59 |
61 |
62 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023 June Bash
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-async-algorithms",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-async-algorithms",
7 | "state" : {
8 | "revision" : "cca423ff03ab657062f3781847e65a867b51bb01",
9 | "version" : "0.0.3"
10 | }
11 | },
12 | {
13 | "identity" : "swift-clocks",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/pointfreeco/swift-clocks",
16 | "state" : {
17 | "revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
18 | "version" : "0.2.0"
19 | }
20 | },
21 | {
22 | "identity" : "swift-collections",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/apple/swift-collections",
25 | "state" : {
26 | "branch" : "main",
27 | "revision" : "939cfd25234472b4dc91c3caeab304d15bca9a73"
28 | }
29 | },
30 | {
31 | "identity" : "swift-gen",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/junebash/swift-gen",
34 | "state" : {
35 | "branch" : "protocol",
36 | "revision" : "6688ffd930314f79fb23b4a75456d75a09da9d5c"
37 | }
38 | },
39 | {
40 | "identity" : "swift-identified-collections",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/pointfreeco/swift-identified-collections.git",
43 | "state" : {
44 | "revision" : "f52eee28bdc6065aa2f8424067e6f04c74bda6e6",
45 | "version" : "0.7.1"
46 | }
47 | },
48 | {
49 | "identity" : "swift-lock",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/junebash/swift-lock",
52 | "state" : {
53 | "branch" : "main",
54 | "revision" : "0ddc399f8aa684af1c29d3069fb2f9765eb03739"
55 | }
56 | },
57 | {
58 | "identity" : "swift-log",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/apple/swift-log.git",
61 | "state" : {
62 | "revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
63 | "version" : "1.5.2"
64 | }
65 | },
66 | {
67 | "identity" : "swift-uuid",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/junebash/swift-uuid",
70 | "state" : {
71 | "branch" : "main",
72 | "revision" : "096b90e8058d9d9d51287a3a534b43ad3c4efcd9"
73 | }
74 | },
75 | {
76 | "identity" : "xctest-dynamic-overlay",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
79 | "state" : {
80 | "revision" : "5a5457a744239896e9b0b03a8e1a5069c3e7b91f",
81 | "version" : "0.6.0"
82 | }
83 | }
84 | ],
85 | "version" : 2
86 | }
87 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "swift-lark",
7 | platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16)],
8 | products: [
9 | .library(
10 | name: "swift-lark",
11 | targets: ["Lark"]
12 | ),
13 | .executable(name: "LarkExample", targets: ["LarkExample"]),
14 | .library(name: "SDL2Image", targets: ["SDL2Image"]),
15 | ],
16 | dependencies: [
17 | .package(url: "https://github.com/apple/swift-async-algorithms", from: Version(0, 0, 3)),
18 | .package(url: "https://github.com/apple/swift-log", from: Version(1, 5, 2)),
19 | .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: Version(0, 6, 0)),
20 | .package(url: "https://github.com/pointfreeco/swift-clocks", from: Version(0, 2, 0)),
21 | .package(url: "https://github.com/junebash/swift-uuid", branch: "main"),
22 | .package(url: "https://github.com/junebash/swift-lock", branch: "main"),
23 | .package(url: "https://github.com/apple/swift-collections", branch: "main"),
24 | .package(
25 | url: "https://github.com/pointfreeco/swift-identified-collections.git",
26 | .upToNextMajor(from: Version(0, 7, 1))
27 | ),
28 | .package(url: "https://github.com/junebash/swift-gen", branch: "protocol"),
29 | ],
30 | targets: [
31 | .target(
32 | name: "Lark",
33 | dependencies: [
34 | "SDL2",
35 | "SDL2Image",
36 | .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
37 | .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
38 | .product(name: "Clocks", package: "swift-clocks"),
39 | .product(name: "Logging", package: "swift-log"),
40 | .product(name: "UUID", package: "swift-uuid"),
41 | .product(name: "Lock", package: "swift-lock"),
42 | .product(name: "Collections", package: "swift-collections"),
43 | .product(name: "IdentifiedCollections", package: "swift-identified-collections"),
44 | .product(name: "Gen", package: "swift-gen"),
45 | ],
46 | swiftSettings: [.unsafeFlags(["-Xfrontend", "-warn-concurrency"])]
47 | ),
48 |
49 | .executableTarget(
50 | name: "LarkExample",
51 | dependencies: ["Lark"],
52 | resources: [.copy("assets")]
53 | ),
54 |
55 | .target(
56 | name: "SDL2",
57 | dependencies: [
58 | "CSDL2"
59 | ]
60 | ),
61 | .systemLibrary(
62 | name: "CSDL2",
63 | pkgConfig: "sdl2",
64 | providers: [
65 | .brew(["sdl2"]),
66 | .apt(["libsdl2-dev"]),
67 | ]
68 | ),
69 |
70 | .target(
71 | name: "SDL2Image",
72 | dependencies: [
73 | "CSDL2Image",
74 | "SDL2",
75 | ]
76 | ),
77 | .systemLibrary(
78 | name: "CSDL2Image",
79 | path: "Sources/CSDL2Image",
80 | pkgConfig: "SDL2_image",
81 | providers: [
82 | .brew(["SDL2_image"]),
83 | .apt(["libsdl2-image-dev"]),
84 | ]
85 | ),
86 |
87 | .testTarget(
88 | name: "LarkTests",
89 | dependencies: ["Lark"],
90 | swiftSettings: [.unsafeFlags(["-Xfrontend", "-warn-concurrency"])]
91 | ),
92 | ]
93 | )
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swift-lark
2 |
3 | A very-much-in-progress, hopefully-eventually-cross-platform ECS game engine made in Swift.
4 |
5 | Here be dragons; use at your own risk.
6 |
--------------------------------------------------------------------------------
/Sources/CSDL2/apple_iOS.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | //#include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 | #include
50 | #include
51 | #include
52 | #include
53 | #include
54 | #include
55 | //#include
56 | //#include
57 | //#include
58 | //#include
59 | //#include
60 | //#include
61 |
62 | // -- image --
63 | #include
64 |
--------------------------------------------------------------------------------
/Sources/CSDL2/apple_macos.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 | #include
50 | #include
51 | // --- testing ----
52 | #include
53 | #include
54 | #include
55 | #include
56 | #include
57 | #include
58 | #include
59 | #include
60 | #include
61 | #include
62 | #include
63 | #include
64 | #include
65 |
--------------------------------------------------------------------------------
/Sources/CSDL2/linux.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 | #include
50 | #include
51 | #include
52 | #include
53 | #include
54 | #include
55 | #include
56 | #include
57 | #include
58 | #include
59 | #include
60 | #include
61 | #include
62 | #include
63 | #include
64 |
65 | // -- image --
66 | #include
67 |
--------------------------------------------------------------------------------
/Sources/CSDL2/module.modulemap:
--------------------------------------------------------------------------------
1 | module CSDL2 [system][extern_c] {
2 | umbrella header "shims.h"
3 | link "SDL2"
4 | export *
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/CSDL2/shims.h:
--------------------------------------------------------------------------------
1 | //
2 | //
3 | #if __APPLE__
4 | #include
5 | #if TARGET_IPHONE_SIMULATOR
6 | #include "apple_iOS.h" // iOS Simulator
7 | #elif TARGET_OS_IPHONE
8 | #include "apple_iOS.h" // iOS device
9 | #elif TARGET_OS_MAC
10 | #include "apple_macOS.h" // macOS
11 | #else
12 | error "Unknown Apple platform"
13 | #endif
14 | #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
15 | #if __has_include("windows_generated.h")
16 | #include "windows_generated.h"
17 | #else
18 | error "windows_generated.h missing - run `genshim.ps1`"
19 | #endif
20 | #elif __linux__
21 | #include "linux.h" // linux
22 | #else
23 | error "Unsupported platform"
24 | #endif
25 |
26 | // SDL_KeyCode was introduced with 2.0.12
27 | //
28 | #if SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION == 0 && SDL_PATCHLEVEL <= 12
29 | typedef int SDL_KeyCode;
30 | #endif
31 |
--------------------------------------------------------------------------------
/Sources/CSDL2Image/module.modulemap:
--------------------------------------------------------------------------------
1 | module CSDL2Image [system][extern_c] {
2 | umbrella header "shims.h"
3 | link "SDL2_image"
4 | link "SDL2"
5 | export *
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/CSDL2Image/shims.h:
--------------------------------------------------------------------------------
1 | //#ifdef __APPLE__
2 | // #include "/usr/local/include/SDL2/SDL.h"
3 | // #include "/usr/local/include/SDL2/SDL_image.h"
4 | //#else
5 | // //#include "/usr/include/SDL2/SDL.h"
6 | // //#include "/usr/include/SDL2/SDL_image.h"
7 | // #include
8 | // #include
9 | //#endif
10 |
11 | #include
12 | #include
13 |
--------------------------------------------------------------------------------
/Sources/Lark/AssetManagement/AssetID.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public struct Asset: Identifiable {
22 | public struct ID: Hashable, RawRepresentable, ExpressibleByStringLiteral {
23 | public var rawValue: String
24 |
25 | public init(rawValue: String) {
26 | self.init(rawValue)
27 | }
28 |
29 | public init(_ rawValue: String) {
30 | self.rawValue = rawValue
31 | }
32 |
33 | public init(stringLiteral: String) {
34 | self.init(stringLiteral)
35 | }
36 | }
37 |
38 | public var id: ID
39 | public var path: String
40 |
41 | public init(id: ID, path: String) {
42 | self.id = id
43 | self.path = path
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/Lark/AssetManagement/AssetStore.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @MainActor
22 | public final class AssetStore {
23 | public enum Failure: Error {
24 | case noResourcePath
25 | }
26 |
27 | private var textures: [Asset.ID: Texture] = [:]
28 |
29 | @Environment(\.resourcePath) private var resourcePath
30 |
31 | public nonisolated init() {}
32 |
33 | public func clear(keepingCapacity: Bool = false) {
34 | textures.removeAll(keepingCapacity: keepingCapacity)
35 | }
36 |
37 | public func addTexture(_ texture: Texture, for id: Asset.ID) {
38 | textures[id] = texture
39 | }
40 |
41 | @discardableResult
42 | public func addTexture(
43 | for asset: Asset,
44 | with renderer: Renderer
45 | ) throws -> Texture {
46 | let texture = try Texture(
47 | renderer: renderer,
48 | surface: Surface(path: resolvedPath(for: asset.path))
49 | )
50 | textures[asset.id] = texture
51 | return texture
52 | }
53 |
54 | public func texture(for id: Asset.ID) -> Texture? {
55 | textures[id]
56 | }
57 |
58 | private func resolvedPath(for path: String) throws -> String {
59 | guard let resourcePath else { throw Failure.noResourcePath }
60 | switch (resourcePath.last, path.first) {
61 | case ("/", "/"):
62 | return resourcePath.dropLast() + path
63 | case ("/", _), (_, "/"):
64 | return resourcePath + path
65 | default:
66 | return resourcePath + "/" + path
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Sources/Lark/AssetManagement/ResourcePath.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import Foundation
22 |
23 | private enum ResourcePathKey: EnvironmentKey {
24 | static let defaultValue: String? = Bundle.main.resourcePath
25 | }
26 |
27 | extension EnvironmentValues {
28 | public var resourcePath: String? {
29 | get { self[ResourcePathKey.self] }
30 | set { self[ResourcePathKey.self] = newValue }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Component.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public protocol Component {}
22 |
23 | extension Component {
24 | @inlinable
25 | static var objectID: ObjectIdentifier { ObjectIdentifier(Self.self) }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/ComponentProxy.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @propertyWrapper
22 | @MainActor
23 | public final class ComponentProxy {
24 | @available(*, unavailable, message: "`Proxy` is only available from types conforming to `Entity`")
25 | public var wrappedValue: C {
26 | get { fatalError() }
27 | set { fatalError() }
28 | }
29 |
30 | public static subscript(
31 | _enclosingInstance instance: E,
32 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
33 | storage storageKeyPath: ReferenceWritableKeyPath>
34 | ) -> C {
35 | get {
36 | storage(instance, wrapped: wrappedKeyPath, storage: storageKeyPath).wrappedValue
37 | }
38 | set {
39 | storage(instance, wrapped: wrappedKeyPath, storage: storageKeyPath).wrappedValue = newValue
40 | }
41 | }
42 |
43 | private static func storage(
44 | _ instance: E,
45 | wrapped wrappedKeyPath: ReferenceWritableKeyPath,
46 | storage storageKeyPath: ReferenceWritableKeyPath>
47 | ) -> Storage {
48 | if let storage = instance[keyPath: storageKeyPath].storage {
49 | return storage
50 | } else {
51 | assertionFailure("Proxy wasn't initialized when expected")
52 | return instance[keyPath: storageKeyPath].initializeStorage(
53 | instance: instance,
54 | registry: instance.registry
55 | )
56 | }
57 | }
58 |
59 | private let fallbackValue: C
60 | private var storage: Storage?
61 |
62 | public init(wrappedValue: C) {
63 | self.fallbackValue = wrappedValue
64 | }
65 | }
66 |
67 | extension ComponentProxy {
68 | final class Storage: Sendable {
69 | let registry: Registry
70 | let id: EntityID
71 |
72 | @MainActor
73 | var wrappedValue: C {
74 | _read { yield registry[C.self, for: id] }
75 | _modify { yield ®istry[C.self, for: id] }
76 | set { registry[C.self, for: id] = newValue }
77 | }
78 |
79 | @MainActor
80 | init(_ initialValue: C, registry: Registry, id: EntityID) {
81 | self.registry = registry
82 | self.id = id
83 |
84 | self.registry.addComponent(initialValue, for: id)
85 | }
86 |
87 | deinit {
88 | // TODO: Remove task when isolated deinits are stable
89 | Task { @MainActor [registry, id] in
90 | registry.removeComponent(C.self, for: id)
91 | }
92 | }
93 | }
94 | }
95 |
96 | @MainActor
97 | internal protocol _Proxy: AnyObject {
98 | associatedtype Storage
99 |
100 | @discardableResult
101 | func initializeStorage(instance: some Entity, registry: Registry) -> Storage
102 | }
103 |
104 | extension ComponentProxy: _Proxy {
105 | @discardableResult
106 | internal func initializeStorage(instance: some Entity, registry: Registry) -> Storage {
107 | if let storage {
108 | assertionFailure("Attempting to initialize storage twice")
109 | return storage
110 | }
111 | let storage = Storage(
112 | fallbackValue,
113 | registry: instance.registry,
114 | id: instance.id
115 | )
116 | self.storage = storage
117 | return storage
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/ComponentSignature.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import BitCollections
22 |
23 | public struct ComponentSignature: Equatable, Configurable, EmptyInitializable {
24 | public var components: Set = []
25 |
26 | public init() {}
27 |
28 | public mutating func requireComponent(_: T.Type) {
29 | components.insert(T.objectID)
30 | }
31 |
32 | public mutating func removeComponent(_: T.Type) {
33 | components.remove(T.objectID)
34 | }
35 |
36 | public func hasComponent(_: T.Type) -> Bool {
37 | components.contains(T.objectID)
38 | }
39 |
40 | public func isSubset(of other: ComponentSignature) -> Bool {
41 | components.isSubset(of: other.components)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Components/RigidBodyComponent.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public struct RigidBodyComponent: Hashable, Component {
22 | public var velocity: FVector2
23 |
24 | public init(velocity: FVector2) {
25 | self.velocity = velocity
26 | }
27 |
28 | public init() {
29 | self.velocity = .zero
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Components/SpriteComponent.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public struct SpriteComponent: Component {
22 | public var textureAssetID: Asset.ID
23 | public var source: IRect?
24 | public var rotationCenter: FVector2? = nil
25 | public var flip: AxisSet
26 |
27 | public init(
28 | textureAssetID: Asset.ID,
29 | source: IRect? = nil,
30 | rotationCenter: FVector2? = nil,
31 | flip: AxisSet = .none
32 | ) {
33 | self.textureAssetID = textureAssetID
34 | self.source = source
35 | self.rotationCenter = rotationCenter
36 | self.flip = flip
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Components/TransformComponent.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public struct TransformComponent: Hashable, Component {
22 | public var position: FVector2
23 | public var scale: FVector2
24 | public var rotation: Angle
25 |
26 | public init(
27 | position: FVector2 = .zero,
28 | scale: FVector2 = .unit,
29 | rotation: Angle = .zero
30 | ) {
31 | self.position = position
32 | self.scale = scale
33 | self.rotation = rotation
34 | }
35 |
36 | public init() {
37 | self.init(position: .zero, scale: .zero, rotation: .zero)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Entity.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public struct EntityID: Hashable, RawRepresentable, Sendable {
22 | public var rawValue: UInt64
23 |
24 | public init(rawValue: UInt64) {
25 | self.rawValue = rawValue
26 | }
27 | }
28 |
29 | extension EntityID: CustomStringConvertible {
30 | public var description: String { rawValue.description }
31 | }
32 |
33 | @MainActor
34 | public protocol Entity: AnyObject, Identifiable where ID == EntityID {
35 | var id: EntityID { get }
36 | var registry: Registry { get }
37 |
38 | init(id: ID, registry: Registry)
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Internal/ComponentPool.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | internal final class ComponentPool {
22 | private var values: [EntityID: C] = [:]
23 |
24 | var count: Int { values.count }
25 |
26 | init() {}
27 |
28 | subscript(entityID entityID: EntityID) -> C {
29 | _read {
30 | yield values[
31 | entityID,
32 | default: {
33 | preconditionFailure("Expected component \(C.self) for entity \(entityID)")
34 | }()]
35 | }
36 | _modify {
37 | yield &values[
38 | entityID,
39 | default: {
40 | preconditionFailure("Expected component \(C.self) for entity \(entityID)")
41 | }()]
42 | }
43 | set {
44 | values[
45 | entityID,
46 | default: {
47 | preconditionFailure("Expected component \(C.self) for entity \(entityID)")
48 | }()] = newValue
49 | }
50 | }
51 |
52 | func set(_ component: C, for entity: EntityID) {
53 | values[entity] = component
54 | }
55 |
56 | func getComponent(for entity: EntityID) -> C? {
57 | values[entity]
58 | }
59 |
60 | func removeComponent(for entity: EntityID) {
61 | values.removeValue(forKey: entity)
62 | }
63 |
64 | func clear() {
65 | values.removeAll(keepingCapacity: true)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Internal/ComponentPoolManager.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | private protocol _ComponentPool {
22 | func clear()
23 | func removeComponent(for entity: EntityID)
24 | }
25 |
26 | extension ComponentPool: _ComponentPool {}
27 |
28 | internal final class ComponentPoolManager {
29 | private var componentPools: [ObjectIdentifier: Any] = [:]
30 |
31 | func pool(for: C.Type) -> ComponentPool {
32 | if let pool = componentPools[C.objectID] as? ComponentPool {
33 | return pool
34 | } else {
35 | let pool = ComponentPool()
36 | componentPools[C.objectID] = pool
37 | return pool
38 | }
39 | }
40 |
41 | func clearAll() {
42 | for pool in componentPools.values.lazy.compactMap(_anyPool(from:)) {
43 | pool.clear()
44 | }
45 | }
46 |
47 | func removeAllComponents(for entityID: EntityID) {
48 | for pool in componentPools.values.lazy.compactMap(_anyPool(from:)) {
49 | pool.removeComponent(for: entityID)
50 | }
51 | }
52 |
53 | private func _anyPool(from value: Any) -> (any _ComponentPool)? {
54 | guard let pool = value as? any _ComponentPool else {
55 | assertionFailure("Expected non-ComponentPool in ComponentPoolManager: \(value)")
56 | return nil
57 | }
58 | return pool
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Internal/EntityLifetimeManager.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | internal struct EntityLifetimeManager {
22 | @Environment(\.logger) var logger
23 |
24 | private var entityCount: UInt64 = 0
25 | private var entitiesToBeAdded: [EntityID] = []
26 | private var entitiesToBeRemoved: [EntityID] = []
27 |
28 | internal mutating func createEntityID() -> EntityID {
29 | defer { entityCount += 1 }
30 |
31 | let entityID = EntityID(rawValue: entityCount)
32 | entitiesToBeAdded.append(entityID)
33 |
34 | logger.log(level: .info, "Entity created with ID: \(entityID)")
35 |
36 | return entityID
37 | }
38 |
39 | internal mutating func withEntitiesToAdd(_ operation: (_ toAdd: [EntityID]) -> Void) {
40 | operation(entitiesToBeAdded)
41 | entitiesToBeAdded.removeAll(keepingCapacity: true)
42 | }
43 |
44 | internal mutating func withEntitiesToRemove(_ operation: (_ toRemove: [EntityID]) -> Void) {
45 | operation(entitiesToBeRemoved)
46 | entitiesToBeRemoved.removeAll(keepingCapacity: true)
47 | }
48 |
49 | internal mutating func removeEntity(_ entityID: EntityID) {
50 | entitiesToBeRemoved.append(entityID)
51 | logger.log(level: .info, "Removing entity with ID: \(entityID)")
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Internal/SystemComponentProxy.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @propertyWrapper
22 | @MainActor
23 | internal struct SystemComponentProxy {
24 | private var fallback: C?
25 | private let registry: Registry
26 | private let entityID: EntityID
27 |
28 | var wrappedValue: C {
29 | mutating get {
30 | if let fallback {
31 | return fallback
32 | } else {
33 | fallback = registry[C.self, for: entityID]
34 | return fallback!
35 | }
36 | }
37 | _modify {
38 | yield ®istry[C.self, for: entityID]
39 | }
40 | set {
41 | fallback = newValue
42 | registry[C.self, for: entityID] = newValue
43 | }
44 | }
45 |
46 | init(_: C.Type = C.self, registry: Registry, entityID: EntityID) {
47 | self.registry = registry
48 | self.entityID = entityID
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Internal/SystemsManager.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @MainActor
22 | internal final class SystemsManager {
23 | private var systems: [ObjectIdentifier: any System] = [:]
24 | @Environment(\.logger) private var logger
25 |
26 | internal init() {}
27 |
28 | subscript(type: S.Type) -> S? {
29 | _read { yield systems[S.typeID] as? S }
30 | }
31 |
32 | subscript(type: S.Type, default defaultValue: @autoclosure () -> S) -> S {
33 | get {
34 | if let system = system(S.self) {
35 | return system
36 | } else {
37 | let system = defaultValue()
38 | setSystem(system)
39 | return system
40 | }
41 | }
42 | _modify {
43 | var system = self.system() ?? defaultValue()
44 | yield &system
45 | self.setSystem(system)
46 | }
47 | set {
48 | self.setSystem(newValue)
49 | }
50 | }
51 |
52 | func system(_: S.Type = S.self) -> S? {
53 | systems[S.typeID] as? S
54 | }
55 |
56 | func addSystem(_ system: S) {
57 | setSystem(system)
58 | }
59 |
60 | func setSystem(_ system: S?) {
61 | systems[S.typeID] = system
62 | }
63 |
64 | func hasSystem(_: S.Type) -> Bool {
65 | systems.keys.contains(ObjectIdentifier(S.self))
66 | }
67 |
68 | func removeSystem(_: S.Type) {
69 | systems.removeValue(forKey: ObjectIdentifier(S.self))
70 | }
71 |
72 | func addEntity(
73 | _ entityID: EntityID,
74 | toSystemsWithSignature signature: ComponentSignature
75 | ) {
76 | logger.trace("Adding entity \(entityID) to systems")
77 | for (key, system) in systems where system.canOperate(on: signature) {
78 | logger.trace("Adding entity \(entityID) to \(type(of: system))")
79 | systems[key]!.entityIDs.add(entityID)
80 | }
81 | }
82 |
83 | func update(registry: __shared Registry, deltaTime: __shared LarkDuration) throws {
84 | for key in systems.keys {
85 | try systems[key]!.update(registry: registry, deltaTime: deltaTime)
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Registry.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @MainActor
22 | public final class Registry: Sendable {
23 | private var componentPools: ComponentPoolManager = .init()
24 | private var systems: SystemsManager = .init()
25 | private var entityLifetime: EntityLifetimeManager = .init()
26 | private var entityComponentSignatures: [EntityID: ComponentSignature] = [:]
27 |
28 | @Environment(\.logger) var logger
29 |
30 | public nonisolated init() {}
31 |
32 | // MARK: - Public Methods
33 |
34 | public func createEntity(_: E.Type) throws -> E {
35 | let entityID = entityLifetime.createEntityID()
36 | entityComponentSignatures[entityID] = .init()
37 | let entity = E(id: entityID, registry: self)
38 |
39 | // initialize proxies
40 | let mirror = Mirror(reflecting: entity)
41 | for child in mirror.children {
42 | guard let proxy = child.value as? any _Proxy else { continue }
43 | _ = proxy.initializeStorage(instance: entity, registry: self)
44 | }
45 |
46 | return entity
47 | }
48 |
49 | public func removeEntity(_: some Entity) {}
50 |
51 | public func removeComponent(_: C.Type, for entityID: EntityID) {
52 | entityComponentSignatures[entityID]?.removeComponent(C.self)
53 | // pool(for: C.self).removeComponent(for: entity)
54 | }
55 |
56 | public func entity(_ entity: EntityID, hasComponent: C.Type) -> Bool {
57 | entityComponentSignatures[entity]?.hasComponent(C.self) ?? false
58 | }
59 |
60 | public func addSystem(_ system: S) {
61 | systems.addSystem(system)
62 | }
63 |
64 | public func removeSystem(_: S.Type) {
65 | systems.removeSystem(S.self)
66 | }
67 |
68 | public func update(deltaTime: LarkDuration) throws {
69 | updateEntities()
70 | try systems.update(registry: self, deltaTime: deltaTime)
71 | }
72 |
73 | // MARK: - Internal Methods
74 |
75 | internal subscript(
76 | _: C.Type,
77 | for entityID: EntityID
78 | ) -> C {
79 | _read { yield componentPools.pool(for: C.self)[entityID: entityID] }
80 | _modify { yield &componentPools.pool(for: C.self)[entityID: entityID] }
81 | set { componentPools.pool(for: C.self)[entityID: entityID] = newValue }
82 | }
83 |
84 | internal func addComponent(_ component: C, for entity: EntityID) {
85 | componentPools.pool(for: C.self).set(component, for: entity)
86 | entityComponentSignatures[entity, default: .init()].requireComponent(C.self)
87 | logger.info("Component \(C.self) added for entity \(entity)")
88 | }
89 |
90 | // MARK: - Private Methods
91 |
92 | private func updateEntities() {
93 | entityLifetime.withEntitiesToAdd { toAdd in
94 | for entityID in toAdd {
95 | guard let signature = entityComponentSignatures[entityID] else {
96 | assertionFailure("Entity \(entityID) created without signature")
97 | continue
98 | }
99 | systems.addEntity(entityID, toSystemsWithSignature: signature)
100 | }
101 | }
102 | entityLifetime.withEntitiesToRemove { toRemove in
103 | for _ in toRemove {
104 | // TODO: Remove the entities that are waiting to be removed from the active systems
105 | }
106 | }
107 | }
108 |
109 | private func addEntityToSystems(_ entityID: EntityID) {
110 | guard let signature = entityComponentSignatures[entityID] else { return }
111 | systems.addEntity(entityID, toSystemsWithSignature: signature)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/System.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import IdentifiedCollections
22 | import OrderedCollections
23 |
24 | @MainActor
25 | public protocol System: AnyObject {
26 | var componentSignature: ComponentSignature { get }
27 | var entityIDs: EntityIDStore { get set }
28 |
29 | func update(registry: __shared Registry, deltaTime: __shared LarkDuration) throws
30 | }
31 |
32 | extension System {
33 | @inlinable
34 | internal static var typeID: ObjectIdentifier { ObjectIdentifier(Self.self) }
35 |
36 | @inlinable
37 | public func canOperate(on otherSignature: ComponentSignature) -> Bool {
38 | componentSignature.isSubset(of: otherSignature)
39 | }
40 | }
41 |
42 | public struct EntityIDStore {
43 | public private(set) var values: OrderedSet = []
44 |
45 | public init() {}
46 |
47 | public mutating func add(_ entity: EntityID) {
48 | values.append(entity)
49 | }
50 |
51 | public mutating func remove(_ entity: EntityID) {
52 | values.remove(entity)
53 | }
54 | }
55 |
56 | extension EntityIDStore: Sequence {
57 | public typealias Iterator = OrderedSet.Iterator
58 |
59 | public func makeIterator() -> OrderedSet.Iterator {
60 | values.makeIterator()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Systems/MovementSystem.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @MainActor
22 | public final class MovementSystem: System {
23 | public let componentSignature: ComponentSignature
24 |
25 | public var entityIDs: EntityIDStore = .init()
26 |
27 | @Environment(\.logger) private var logger
28 |
29 | public init() {
30 | self.componentSignature = ComponentSignature {
31 | $0.requireComponent(TransformComponent.self)
32 | $0.requireComponent(RigidBodyComponent.self)
33 | }
34 | }
35 |
36 | public func update(registry: __shared Registry, deltaTime: __shared LarkDuration) {
37 | for entityID in entityIDs {
38 | @SystemComponentProxy(registry: registry, entityID: entityID)
39 | var transform: TransformComponent
40 |
41 | @SystemComponentProxy(registry: registry, entityID: entityID)
42 | var rigidBody: RigidBodyComponent
43 |
44 | transform.position += rigidBody.velocity
45 | logger.trace("Position for \(entityID) is now \(transform.position)")
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/Lark/ECS/Systems/RenderingSystem.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public final class RenderingSystem: System {
22 | public var entityIDs: EntityIDStore = .init()
23 | public let componentSignature: ComponentSignature
24 |
25 | private let renderer: Renderer
26 | private let assetStore: AssetStore
27 |
28 | public init(renderer: Renderer, assetStore: AssetStore) {
29 | self.componentSignature = ComponentSignature {
30 | $0.requireComponent(TransformComponent.self)
31 | $0.requireComponent(SpriteComponent.self)
32 | }
33 | self.renderer = renderer
34 | self.assetStore = assetStore
35 | }
36 |
37 | public func update(registry: __shared Registry, deltaTime: __shared LarkDuration) throws {
38 | renderer.setColor(Color(red: 0, green: 0.1, blue: 0.1))
39 | try renderer.clear()
40 |
41 | for entityID in entityIDs {
42 | @SystemComponentProxy(registry: registry, entityID: entityID)
43 | var transform: TransformComponent
44 |
45 | @SystemComponentProxy(registry: registry, entityID: entityID)
46 | var sprite: SpriteComponent
47 |
48 | let texture = try assetStore.texture(for: sprite.textureAssetID).orThrow()
49 |
50 | try renderer.renderCopy(
51 | texture,
52 | source: sprite.source,
53 | destination: Rect(
54 | origin: transform.position,
55 | size: FSize2(texture.size) * transform.scale
56 | ),
57 | rotation: transform.rotation,
58 | center: sprite.rotationCenter,
59 | flip: sprite.flip
60 | )
61 | }
62 |
63 | renderer.present()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/Lark/Engine/Engine.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import AsyncAlgorithms
22 | import Clocks
23 | import Foundation
24 | import Logging
25 | import SDL2
26 |
27 | @MainActor
28 | public final class Engine {
29 | public struct Configuration {
30 | public var initOptions: EngineInitOptions = .everything
31 | public var logFrameTiming: Bool = false
32 | public var targetFrameDuration: LarkDuration = .fps60
33 | public var updateClock: LarkClock = .live
34 |
35 | public init() {}
36 | }
37 |
38 | public let configuration: Configuration
39 |
40 | public let registry: Registry = .init()
41 |
42 | @Environment(\.logger) private var logger
43 |
44 | private var updateClock: LarkClock { configuration.updateClock }
45 |
46 | public init(configuration: Configuration = .init()) throws {
47 | @Environment(\.logger) var logger
48 |
49 | self.configuration = configuration
50 |
51 | do {
52 | try withThrowingSDL {
53 | SDL_Init(configuration.initOptions.rawValue)
54 | }
55 | } catch {
56 | logger.critical("Error setting up engine: \(error)")
57 | throw error
58 | }
59 |
60 | logger.info("Game engine initialized")
61 | }
62 |
63 | public func run(_ game: __owned some GameProtocol) {
64 | logger.trace("Running game engine")
65 | defer {
66 | logger.trace("Engine finished running")
67 | }
68 |
69 | var lastUpdate = updateClock.now
70 | do {
71 | var currentState = game
72 | while currentState.isRunning {
73 | let frameStart = updateClock.now
74 | let deltaTime = lastUpdate.duration(to: frameStart)
75 | lastUpdate = frameStart
76 | try currentState.activeScene?.update(deltaTime: deltaTime)
77 | if let nextScene = currentState.activeScene?.nextScene() {
78 | currentState.activeScene = nil // nil out previous scene first to free resources
79 | currentState.activeScene = try nextScene()
80 | }
81 | try registry.update(deltaTime: deltaTime)
82 | // movementSystem.update()
83 | // collisionSystem.update()
84 | // damageSystem.update()
85 | // let frameEnd = updateClock.now
86 | // let leftoverTime = configuration.targetFrameDuration - frameStart.duration(to: frameEnd)
87 | // if leftoverTime > .zero {
88 | // updateClock.sleep(for: leftoverTime)
89 | // }
90 | }
91 | } catch {
92 | logger.critical("Error in game loop: \(error)")
93 | }
94 | }
95 |
96 | deinit {
97 | Self.quit()
98 | }
99 |
100 | private nonisolated static func quit() {
101 | SDL_Quit()
102 | EnvironmentValues.current.logger.trace("Engine quit")
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/Lark/Engine/EngineInitOptions.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SDL2
22 |
23 | public struct EngineInitOptions: OptionSet {
24 | public private(set) var rawValue: UInt32
25 |
26 | public init(rawValue: UInt32) {
27 | self.rawValue = rawValue
28 | }
29 |
30 | public static let timer: Self = .init(rawValue: SDL_INIT_TIMER)
31 | public static let audio: Self = .init(rawValue: SDL_INIT_AUDIO)
32 | public static let video: Self = .init(rawValue: SDL_INIT_VIDEO)
33 | public static let joystick: Self = .init(rawValue: SDL_INIT_JOYSTICK)
34 | public static let haptic: Self = .init(rawValue: SDL_INIT_HAPTIC)
35 | public static let gameController: Self = .init(rawValue: SDL_INIT_GAMECONTROLLER)
36 | public static let events: Self = .init(rawValue: SDL_INIT_EVENTS)
37 | public static let sensor: Self = .init(rawValue: SDL_INIT_SENSOR)
38 | public static let everything: Self = [
39 | .timer, .audio, .haptic, .gameController, .events, .sensor, .video,
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Lark/Engine/Game.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | @MainActor
22 | public protocol GameProtocol {
23 | var isRunning: Bool { get }
24 |
25 | var activeScene: (any Scene)? { get set }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Lark/Engine/LarkError.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | public enum LarkError: Error {
22 | case sdl(SDLError)
23 | case unknown
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Lark/Engine/SDLError.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023 June Bash
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in all
11 | // copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | // SOFTWARE.
20 |
21 | import SDL2
22 |
23 | public struct SDLError: Error {
24 | public var code: Int32?
25 | public var description: String
26 |
27 | @inlinable
28 | init(code: Int32? = nil, description: String = "") {
29 | self.code = code
30 | self.description = description
31 | }
32 |
33 | @inlinable
34 | static func get(code: Int32? = nil) -> SDLError? {
35 | if let messagePointer = SDL_GetError(), messagePointer.pointee != 0 {
36 | defer { SDL_ClearError() }
37 | return .init(code: code, description: .init(cString: messagePointer))
38 | } else {
39 | return nil
40 | }
41 | }
42 | }
43 |
44 | @inlinable
45 | func withThrowingSDL(_ operation: () -> Int32) throws {
46 | let opCode = operation()
47 | if opCode != 0, let error = SDLError.get(code: opCode) {
48 | throw LarkError.sdl(error)
49 | }
50 | }
51 |
52 | @inlinable
53 | func withThrowingSDL