├── .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(_ operation: () -> Output?) throws -> Output { 54 | guard let result = operation() else { 55 | throw SDLError.get().map(LarkError.sdl) ?? .unknown 56 | } 57 | return result 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Lark/Engine/Scene.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 Scene { 23 | typealias SceneProvider = () throws -> any Scene 24 | 25 | mutating func update(deltaTime: LarkDuration) throws 26 | 27 | func nextScene() -> SceneProvider? 28 | } 29 | 30 | extension Scene { 31 | public func update(deltaTime: LarkDuration) {} 32 | public func nextScene() -> SceneProvider? { nil } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Lark/Environment/Environment.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 | public struct Environment { 23 | public let initialValues: EnvironmentValues 24 | public let keyPath: KeyPath 25 | 26 | public var wrappedValue: Value { 27 | initialValues.merging(with: .current)[keyPath: keyPath] 28 | } 29 | 30 | public init(_ keyPath: KeyPath) { 31 | self.initialValues = .current 32 | self.keyPath = keyPath 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Lark/Environment/EnvironmentKey.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 EnvironmentKey { 22 | associatedtype Value: Sendable = Self 23 | 24 | static var defaultValue: Value { get } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Lark/Environment/EnvironmentValues.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 Clocks 22 | 23 | public struct EnvironmentValues: Sendable { 24 | @TaskLocal public static var current: EnvironmentValues = .init() 25 | 26 | @usableFromInline 27 | internal var values: [ObjectIdentifier: any Sendable] = [:] 28 | 29 | @usableFromInline 30 | internal init(values: [ObjectIdentifier: any Sendable]) { 31 | self.values = values 32 | } 33 | 34 | public init() {} 35 | 36 | @inlinable 37 | public subscript(key: Key.Type) -> Key.Value { 38 | get { values[ObjectIdentifier(key)] as? Key.Value ?? key.defaultValue } 39 | set { values[ObjectIdentifier(key)] = newValue } 40 | } 41 | 42 | public static func withValues( 43 | _ update: (inout EnvironmentValues) -> Void, 44 | perform operation: () throws -> R 45 | ) rethrows -> R { 46 | var values = Self.current 47 | update(&values) 48 | return try EnvironmentValues.$current.withValue(values, operation: operation) 49 | } 50 | 51 | public static func withValues( 52 | _ update: (inout EnvironmentValues) -> Void, 53 | perform operation: () async throws -> R 54 | ) async rethrows -> R { 55 | var values = Self.current 56 | update(&values) 57 | return try await EnvironmentValues.$current.withValue(values, operation: operation) 58 | } 59 | 60 | public func merging(with other: EnvironmentValues) -> EnvironmentValues { 61 | Self(values: values.merging(other.values, uniquingKeysWith: { $1 })) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Lark/Environment/Logger.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 Logging 22 | 23 | extension Logger: EnvironmentKey { 24 | public static let defaultValue: Self = Logger(label: "Lark", level: .notice) 25 | } 26 | 27 | extension EnvironmentValues { 28 | public var logger: Logger { 29 | get { self[Logger.self] } 30 | set { self[Logger.self] = newValue } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Lark/Environment/UpdateClock.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 Clocks 22 | import SDL2 23 | 24 | extension EnvironmentValues { 25 | public var updateClock: LarkClock { 26 | get { self[UpdateClockKey.self] } 27 | set { self[UpdateClockKey.self] = newValue } 28 | } 29 | } 30 | 31 | private enum UpdateClockKey: EnvironmentKey { 32 | static let defaultValue: LarkClock = .live 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Lark/Events/Event+SDL.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 | extension Event { 24 | @inlinable 25 | init?(event: SDL_Event) { 26 | guard let kind = Event.Kind(event: event) else { return nil } 27 | self = .init(kind: kind, timestamp: EnvironmentValues.current.updateClock.now) 28 | // self = .init(kind: kind, timestamp: EnvironmentValues.current.updateClock.now) 29 | } 30 | } 31 | 32 | extension Event.Kind { 33 | @inlinable 34 | init?(event: SDL_Event) { 35 | switch SDL_EventType(event.type) { 36 | case SDL_QUIT: 37 | self = .quit 38 | case SDL_KEYUP: 39 | self = .keyUp( 40 | KeyEventInfo( 41 | windowID: event.key.windowID, 42 | keyCode: KeyCode(rawValue: .init(event.key.keysym.sym)), 43 | scanCode: ScanCode(event.key.keysym.scancode), 44 | keyMod: KeyMod(rawValue: UInt32(event.key.keysym.mod)), 45 | isRepeat: event.key.repeat != 0 46 | ) 47 | ) 48 | case SDL_KEYDOWN: 49 | self = .keyDown( 50 | KeyEventInfo( 51 | windowID: event.key.windowID, 52 | keyCode: KeyCode(rawValue: .init(event.key.keysym.sym)), 53 | scanCode: ScanCode(event.key.keysym.scancode), 54 | keyMod: KeyMod(rawValue: UInt32(event.key.keysym.mod)), 55 | isRepeat: event.key.repeat != 0 56 | ) 57 | ) 58 | case SDL_APP_TERMINATING: 59 | self = .appTerminating 60 | case SDL_APP_LOWMEMORY: 61 | self = .appLowMemory 62 | case SDL_APP_WILLENTERBACKGROUND: 63 | self = .appWillEnterBackground 64 | case SDL_APP_DIDENTERBACKGROUND: 65 | self = .appDidEnterBackground 66 | case SDL_APP_WILLENTERFOREGROUND: 67 | self = .appWillEnterForeground 68 | case SDL_APP_DIDENTERFOREGROUND: 69 | self = .appDidEnterForeground 70 | case SDL_LOCALECHANGED: 71 | self = .localeDidChange 72 | case SDL_DISPLAYEVENT: 73 | self = .display( 74 | index: Int32(bitPattern: event.display.display), 75 | event: DisplayEvent(sdlDisplayEvent: event.display) 76 | ) 77 | case SDL_WINDOWEVENT: 78 | self = .window( 79 | id: event.window.windowID, 80 | event: WindowEvent(sdlWindowEvent: event.window) 81 | ) 82 | 83 | default: 84 | // TODO: fill out more event types 85 | return nil 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Lark/Events/Event.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 Event: Sendable, Hashable { 22 | public enum Kind: Sendable, Hashable { 23 | case quit 24 | case appTerminating 25 | case appLowMemory 26 | case appWillEnterBackground 27 | case appDidEnterBackground 28 | case appWillEnterForeground 29 | case appDidEnterForeground 30 | case localeDidChange 31 | case display(index: Int32, event: DisplayEvent) 32 | case window(id: UInt32, event: WindowEvent) 33 | case keyDown(KeyEventInfo) 34 | case keyUp(KeyEventInfo) 35 | case textEditing(windowID: UInt32, text: String, start: Int, length: Int) 36 | case textInput(windowID: UInt32, text: String) 37 | case mouse(MouseEventInfo) 38 | case joystick(JoystickEventInfo) 39 | case controller(ControllerEventInfo) 40 | case finger(FingerEventInfo) 41 | case clipboardUpdate 42 | case touch(TouchEventInfo) 43 | case drop(DropEventInfo) 44 | case audioDevice(AudioDeviceEventInfo) 45 | case renderTargetsReset 46 | case renderDeviceReset 47 | case user(UserEventInfo) 48 | } 49 | 50 | public var kind: Kind 51 | public var timestamp: LarkClock.Instant 52 | 53 | @inlinable 54 | public init(kind: Kind, timestamp: LarkClock.Instant) { 55 | self.kind = kind 56 | self.timestamp = timestamp 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Lark/Events/Events.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 | import SDL2 23 | 24 | public struct Events: Sendable { 25 | private var _getEvents: @Sendable () -> [Event] 26 | 27 | public init(getEvents: @escaping @Sendable () -> [Event]) { 28 | self._getEvents = getEvents 29 | } 30 | 31 | public func callAsFunction() -> [Event] { 32 | autoreleasepool { _getEvents() } 33 | } 34 | } 35 | 36 | extension EnvironmentValues { 37 | public var events: Events { 38 | get { self[EventsKey.self] } 39 | set { self[EventsKey.self] = newValue } 40 | } 41 | } 42 | 43 | private enum EventsKey: EnvironmentKey { 44 | typealias Value = Events 45 | 46 | static let defaultValue: Events = Events { Array(Event.Poll()) } 47 | } 48 | 49 | extension Event { 50 | @usableFromInline 51 | struct Poll: Sequence, IteratorProtocol, Sendable { 52 | @inlinable 53 | internal init() {} 54 | 55 | @inlinable 56 | internal func next() -> Event? { 57 | var sdlEvent = SDL_Event() 58 | guard SDL_PollEvent(&sdlEvent) == 1 else { return nil } 59 | return Event(event: sdlEvent) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Lark/Extensions/Duration+.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 | extension Duration { 22 | @inlinable 23 | public func fractionalSeconds(_: T.Type = Float.self) -> T { 24 | T(components.seconds) + (T(components.attoseconds) * 0.000_000_000__000_000_001) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Lark/Extensions/Logger+.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 Logging 22 | 23 | extension Logger { 24 | public init(label: String, level: Logger.Level) { 25 | self.init(label: label) 26 | self.logLevel = level 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Lark/Extensions/Numeric+Extensions.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 | extension FloatingPoint { 22 | @inlinable 23 | public func clamped(to range: ClosedRange) -> Self { 24 | var newValue = self 25 | newValue.clamp(to: range) 26 | return newValue 27 | } 28 | 29 | @inlinable 30 | public mutating func clamp(to range: ClosedRange) { 31 | switch self { 32 | case range.upperBound...: 33 | self = range.upperBound 34 | case .. .zero ? self : nil } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Lark/Extensions/Task+CancellableValue.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 | extension Task where Failure == Error { 22 | public var cancellableValue: Success { 23 | get async throws { 24 | try await withTaskCancellationHandler { 25 | try await self.value 26 | } onCancel: { 27 | self.cancel() 28 | } 29 | } 30 | } 31 | } 32 | 33 | extension Task where Failure == Never { 34 | public var cancellableValue: Success { 35 | get async { 36 | await withTaskCancellationHandler { 37 | await self.value 38 | } onCancel: { 39 | self.cancel() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Lark/FileManagement/File.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 enum File { 24 | public static func getBasePath() throws -> String { 25 | try withThrowingSDL { 26 | SDL_GetBasePath().map { cString in 27 | defer { SDL_free(cString) } 28 | return String(cString: cString) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Lark/Geometry/Angle.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 Angle: Hashable { 22 | public var radians: Double 23 | 24 | public var degrees: Double { 25 | get { radians * Self.toDegreesMultiplier } 26 | set { radians = newValue * Self.toRadiansMultiplier } 27 | } 28 | 29 | private init(radians: Double) { 30 | self.radians = radians 31 | } 32 | 33 | public static func radians(_ value: Double) -> Angle { 34 | Angle(radians: value) 35 | } 36 | 37 | public static func degrees(_ value: Double) -> Angle { 38 | Angle(radians: value * Self.toRadiansMultiplier) 39 | } 40 | 41 | private static let toDegreesMultiplier: Double = 180.0 / .pi 42 | private static let toRadiansMultiplier: Double = .pi / 180.0 43 | } 44 | 45 | extension Angle: AdditiveArithmetic { 46 | public static let zero: Angle = .radians(0) 47 | 48 | public static func + (lhs: Angle, rhs: Angle) -> Angle { 49 | .radians(lhs.radians + rhs.radians) 50 | } 51 | 52 | public static func - (lhs: Angle, rhs: Angle) -> Angle { 53 | .radians(lhs.radians - rhs.radians) 54 | } 55 | 56 | public static func += (lhs: inout Angle, rhs: Angle) { 57 | lhs.radians += rhs.radians 58 | } 59 | 60 | public static func -= (lhs: inout Angle, rhs: Angle) { 61 | lhs.radians -= rhs.radians 62 | } 63 | } 64 | 65 | extension Angle { 66 | public static func * (angle: Angle, multiplier: Double) -> Angle { 67 | .radians(angle.radians * multiplier) 68 | } 69 | 70 | public static func * (angle: Angle, multiplier: some BinaryInteger) -> Angle { 71 | .radians(angle.radians * Double(multiplier)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Lark/Geometry/Rect.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 Rect { 22 | public var origin: Point2 23 | public var size: Size2 24 | 25 | @inlinable 26 | public init(origin: Point2, size: Size2) { 27 | self.origin = origin 28 | self.size = size 29 | } 30 | 31 | @inlinable 32 | public init(x: Value, y: Value, width: Value, height: Value) { 33 | self.init( 34 | origin: .init(x: x, y: y), 35 | size: .init(width: width, height: height) 36 | ) 37 | } 38 | 39 | @inlinable 40 | public init() { 41 | self.init(origin: .zero, size: .zero) 42 | } 43 | 44 | @inlinable 45 | public static var zero: Self { .init() } 46 | } 47 | 48 | extension Rect: Equatable where Value: Equatable {} 49 | extension Rect: Hashable where Value: Hashable {} 50 | extension Rect: Sendable where Value: Sendable {} 51 | 52 | public typealias IRect = Rect 53 | public typealias FRect = Rect 54 | -------------------------------------------------------------------------------- /Sources/Lark/Geometry/Size2.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 Size2 { 22 | public var width: Span 23 | public var height: Span 24 | 25 | @inlinable 26 | public init(width: Span, height: Span) { 27 | self.width = width 28 | self.height = height 29 | } 30 | 31 | @inlinable 32 | public init() { 33 | self.init(width: .zero, height: .zero) 34 | } 35 | 36 | @inlinable 37 | public static var zero: Self { .init() } 38 | } 39 | 40 | extension Size2 where Span: BinaryFloatingPoint { 41 | @inlinable 42 | public init(_ other: Size2) { 43 | self.init(width: Span(other.width), height: Span(other.height)) 44 | } 45 | 46 | @inlinable 47 | public init(_ other: Size2) { 48 | self.init(width: Span(other.width), height: Span(other.height)) 49 | } 50 | } 51 | 52 | extension Size2 where Span: BinaryInteger { 53 | @inlinable 54 | public init(_ other: Size2) { 55 | self.init(width: Span(other.width), height: Span(other.height)) 56 | } 57 | 58 | @inlinable 59 | public init(_ other: Size2) { 60 | self.init(width: Span(other.width), height: Span(other.height)) 61 | } 62 | } 63 | 64 | extension Size2: Equatable where Span: Equatable {} 65 | extension Size2: Hashable where Span: Hashable {} 66 | extension Size2: Sendable where Span: Sendable {} 67 | 68 | extension Size2 { 69 | public static func * (size: Size2, scale: Vector2) -> Size2 { 70 | Size2( 71 | width: size.width * scale.x, 72 | height: size.height * scale.y 73 | ) 74 | } 75 | } 76 | 77 | public typealias ISize2 = Size2 78 | public typealias FSize2 = Size2 79 | -------------------------------------------------------------------------------- /Sources/Lark/Geometry/Vector2.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 typealias Point2 = Vector2 22 | public typealias IVector2 = Vector2 23 | public typealias IPoint2 = Vector2 24 | public typealias FVector2 = Vector2 25 | public typealias FPoint2 = Vector2 26 | 27 | public struct Vector2 { 28 | public var x: Value 29 | public var y: Value 30 | 31 | @inlinable 32 | public init(x: Value = .zero, y: Value = .zero) { 33 | (self.x, self.y) = (x, y) 34 | } 35 | 36 | @inlinable 37 | public init(_ x: Value, _ y: Value) { 38 | self.init(x: x, y: y) 39 | } 40 | } 41 | 42 | extension Vector2: CustomStringConvertible { 43 | public var description: String { "(x: \(x), y: \(y))" } 44 | } 45 | 46 | extension Vector2: Equatable where Value: Equatable {} 47 | extension Vector2: Hashable where Value: Hashable {} 48 | extension Vector2: Sendable where Value: Sendable {} 49 | 50 | extension Vector2: AdditiveArithmetic { 51 | @inlinable 52 | public static var zero: Self { Self(.zero, .zero) } 53 | 54 | @inlinable 55 | public static var unit: Self { Self(1, 1) } 56 | 57 | @inlinable 58 | public static func - (lhs: Vector2, rhs: Vector2) -> Vector2 { 59 | Vector2(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 60 | } 61 | 62 | @inlinable 63 | public static func + (lhs: Vector2, rhs: Vector2) -> Vector2 { 64 | Vector2(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 65 | } 66 | } 67 | 68 | extension Vector2 { 69 | public static func * (vec: Vector2, value: Value) -> Vector2 { 70 | Vector2(x: vec.x * value, y: vec.y * value) 71 | } 72 | 73 | public static func *= (vec: inout Vector2, value: Value) { 74 | vec.x *= value 75 | vec.y *= value 76 | } 77 | 78 | // TODO: up, down, left, right, normalized, etc 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Lark/Input/KeyCode.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 KeyCode: RawRepresentable, Hashable, Sendable { 24 | public var rawValue: UInt32 25 | 26 | public init(rawValue: UInt32) { 27 | self.rawValue = rawValue 28 | } 29 | 30 | internal init(_ sdlKeyCode: SDL_KeyCode) { 31 | self.rawValue = sdlKeyCode.rawValue 32 | } 33 | 34 | public static let backspace: Self = .init(SDLK_BACKSPACE) 35 | public static let tab: Self = .init(SDLK_TAB) 36 | public static let `return`: Self = .init(SDLK_RETURN) 37 | public static let escape: Self = .init(SDLK_ESCAPE) 38 | public static let space: Self = .init(SDLK_SPACE) 39 | public static let exclaim: Self = .init(SDLK_EXCLAIM) 40 | public static let doubleQuote: Self = .init(SDLK_QUOTEDBL) 41 | // TODO: 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Lark/Input/KeyMod.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 KeyMod: OptionSet, Sendable, Hashable { 22 | public var rawValue: UInt32 23 | 24 | public init(rawValue: UInt32) { 25 | self.rawValue = rawValue 26 | } 27 | 28 | public static let leftShift: Self = .init(rawValue: 0x0001) 29 | public static let rightShift: Self = .init(rawValue: 0x0002) 30 | public static let leftControl: Self = .init(rawValue: 0x0040) 31 | public static let rightControl: Self = .init(rawValue: 0x0080) 32 | public static let leftAlt: Self = .init(rawValue: 0x0100) 33 | public static let rightAlt: Self = .init(rawValue: 0x0200) 34 | public static let leftGUI: Self = .init(rawValue: 0x0400) 35 | public static let rightGUI: Self = .init(rawValue: 0x0800) 36 | public static let numLock: Self = .init(rawValue: 0x1000) 37 | public static let capsLock: Self = .init(rawValue: 0x2000) 38 | public static let mode: Self = .init(rawValue: 0x4000) 39 | public static let scroll: Self = .init(rawValue: 0x8000) 40 | 41 | public static let control: Self = .init(rawValue: leftControl.rawValue | rightControl.rawValue) 42 | public static let shift: Self = .init(rawValue: leftShift.rawValue | rightShift.rawValue) 43 | public static let alt: Self = .init(rawValue: leftAlt.rawValue | rightAlt.rawValue) 44 | public static let gui: Self = .init(rawValue: leftGUI.rawValue | rightGUI.rawValue) 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Lark/Input/ScanCode.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 ScanCode: RawRepresentable, Sendable, Hashable { 24 | public var rawValue: UInt32 25 | 26 | @inlinable 27 | public init(rawValue: UInt32) { 28 | self.rawValue = rawValue 29 | } 30 | 31 | @inlinable 32 | internal init(_ sdlScanCode: SDL_Scancode) { 33 | self.rawValue = sdlScanCode.rawValue 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Color.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 Color: Sendable { 24 | @usableFromInline 25 | internal var sdlColor: SDL_Color 26 | 27 | @inlinable 28 | internal init(sdlColor: SDL_Color) { 29 | self.sdlColor = sdlColor 30 | } 31 | 32 | @inlinable 33 | internal static func _normalizeComponent(_ value: Float) -> UInt8 { 34 | UInt8(value.clamped(to: 0.0...1.0) * ._uint8Max) 35 | } 36 | 37 | @inlinable 38 | internal static func _denormalizeComponent(_ value: UInt8) -> Float { 39 | Float(value) * ._uint8MaxReciprocal 40 | } 41 | } 42 | 43 | extension Color: Equatable { 44 | public static func == (lhs: Color, rhs: Color) -> Bool { 45 | return lhs.sdlColor.r == rhs.sdlColor.r 46 | && lhs.sdlColor.g == rhs.sdlColor.g 47 | && lhs.sdlColor.b == rhs.sdlColor.b 48 | && lhs.sdlColor.a == rhs.sdlColor.a 49 | } 50 | } 51 | 52 | extension Color: Hashable { 53 | public func hash(into hasher: inout Hasher) { 54 | hasher.combine(sdlColor.r) 55 | hasher.combine(sdlColor.g) 56 | hasher.combine(sdlColor.b) 57 | hasher.combine(sdlColor.a) 58 | } 59 | } 60 | 61 | extension Color { 62 | @inlinable 63 | public var red: UInt8 { 64 | _read { yield sdlColor.r } 65 | _modify { yield &sdlColor.r } 66 | set { sdlColor.r = newValue } 67 | } 68 | 69 | @inlinable 70 | public var green: UInt8 { 71 | _read { yield sdlColor.g } 72 | _modify { yield &sdlColor.g } 73 | set { sdlColor.g = newValue } 74 | } 75 | 76 | @inlinable 77 | public var blue: UInt8 { 78 | _read { yield sdlColor.b } 79 | _modify { yield &sdlColor.b } 80 | set { sdlColor.b = newValue } 81 | } 82 | 83 | @inlinable 84 | public var alpha: UInt8 { 85 | _read { yield sdlColor.a } 86 | _modify { yield &sdlColor.a } 87 | set { sdlColor.a = newValue } 88 | } 89 | 90 | public struct Floats { 91 | @usableFromInline 92 | internal let _or, _og, _ob, _oa: () -> Float 93 | 94 | @usableFromInline 95 | internal var _nr, _ng, _nb, _na: Float? 96 | 97 | public var red: Float { 98 | _read { yield _nr ?? _or() } 99 | set { _nr = newValue } 100 | } 101 | 102 | public var green: Float { 103 | _read { yield _ng ?? _og() } 104 | set { _ng = newValue } 105 | } 106 | 107 | public var blue: Float { 108 | _read { yield _nb ?? _ob() } 109 | set { _nb = newValue } 110 | } 111 | 112 | public var alpha: Float { 113 | _read { yield _na ?? _oa() } 114 | set { _na = newValue } 115 | } 116 | 117 | @inlinable 118 | internal init( 119 | red: @autoclosure @escaping () -> Float, 120 | green: @autoclosure @escaping () -> Float, 121 | blue: @autoclosure @escaping () -> Float, 122 | alpha: @autoclosure @escaping () -> Float 123 | ) { 124 | self._or = red 125 | self._og = green 126 | self._ob = blue 127 | self._oa = alpha 128 | } 129 | } 130 | 131 | @inlinable 132 | public var floats: Floats { 133 | _read { 134 | yield makeFloats() 135 | } 136 | _modify { 137 | var floats = makeFloats() 138 | yield &floats 139 | } 140 | set(newFloats) { 141 | if let red = newFloats._nr { 142 | self.red = Color._normalizeComponent(red) 143 | } 144 | if let green = newFloats._ng { 145 | self.green = Color._normalizeComponent(green) 146 | } 147 | if let blue = newFloats._nb { 148 | self.blue = Color._normalizeComponent(blue) 149 | } 150 | if let alpha = newFloats._na { 151 | self.alpha = Color._normalizeComponent(alpha) 152 | } 153 | } 154 | } 155 | 156 | @inlinable 157 | internal func makeFloats() -> Floats { 158 | Floats( 159 | red: Color._denormalizeComponent(red), 160 | green: Color._denormalizeComponent(green), 161 | blue: Color._denormalizeComponent(blue), 162 | alpha: Color._denormalizeComponent(alpha) 163 | ) 164 | } 165 | 166 | public init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8 = .max) { 167 | self.init(sdlColor: .init(r: red, g: green, b: blue, a: alpha)) 168 | } 169 | 170 | @_disfavoredOverload 171 | public init(red: Float, green: Float, blue: Float, alpha: Float = 1.0) { 172 | self.init( 173 | red: Color._normalizeComponent(red), 174 | green: Color._normalizeComponent(green), 175 | blue: Color._normalizeComponent(blue), 176 | alpha: Color._normalizeComponent(alpha) 177 | ) 178 | } 179 | 180 | public init(white: UInt8, alpha: UInt8 = .max) { 181 | self.init(red: white, green: white, blue: white, alpha: alpha) 182 | } 183 | 184 | @_disfavoredOverload 185 | public init(white: Float, alpha: Float = 1.0) { 186 | self.init(red: white, green: white, blue: white, alpha: alpha) 187 | } 188 | } 189 | 190 | // MARK: - Statics 191 | 192 | extension Color { 193 | public static let white: Color = .init(white: 1.0) 194 | } 195 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/DisplayMode.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 DisplayMode { 24 | public struct DriverData { 25 | internal let pointer: UnsafeMutableRawPointer 26 | } 27 | 28 | public var format: PixelFormat.Kind 29 | public var size: ISize2 30 | public var refreshRate: Int32? 31 | public let driverData: DriverData 32 | 33 | public static func getCurrent(displayIndex: Int32) throws -> DisplayMode { 34 | var displayMode: SDL_DisplayMode = .init() 35 | try withThrowingSDL { 36 | SDL_GetCurrentDisplayMode(displayIndex, &displayMode) 37 | } 38 | return .init( 39 | format: .unknown, // TODO: - 40 | size: ISize2(width: displayMode.w, height: displayMode.h), 41 | refreshRate: displayMode.refresh_rate.nonZero, 42 | driverData: DriverData(pointer: displayMode.driverdata) 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Files.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 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/PixelFormat.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 Palette { 24 | internal var palettePointer: UnsafeMutablePointer 25 | 26 | init(palettePointer: UnsafeMutablePointer) { 27 | self.palettePointer = palettePointer 28 | } 29 | } 30 | 31 | public final class PixelFormat { 32 | internal var sdlPointer: UnsafeMutablePointer 33 | 34 | private var needsFree: Bool = false 35 | 36 | internal var sdlValue: SDL_PixelFormat { sdlPointer.pointee } 37 | 38 | public var kind: Kind { Kind(rawValue: sdlValue.format) } 39 | 40 | public var palette: Palette { Palette(palettePointer: sdlValue.palette) } 41 | 42 | public var bitsPerPixel: UInt8 { sdlValue.BitsPerPixel } 43 | public var bytesPerPixel: UInt8 { sdlValue.BytesPerPixel } 44 | 45 | public var rMask: UInt32 { sdlValue.Rmask } 46 | public var gMask: UInt32 { sdlValue.Gmask } 47 | public var bMask: UInt32 { sdlValue.Bmask } 48 | public var aMask: UInt32 { sdlValue.Amask } 49 | public var rLoss: UInt8 { sdlValue.Rloss } 50 | public var gLoss: UInt8 { sdlValue.Gloss } 51 | public var bLoss: UInt8 { sdlValue.Bloss } 52 | public var aLoss: UInt8 { sdlValue.Aloss } 53 | public var rShift: UInt8 { sdlValue.Rshift } 54 | public var gShift: UInt8 { sdlValue.Gshift } 55 | public var bShift: UInt8 { sdlValue.Bshift } 56 | public var aShift: UInt8 { sdlValue.Ashift } 57 | public var referenceCount: Int32 { sdlValue.refcount } 58 | 59 | public func next() -> PixelFormat? { 60 | PixelFormat(formatPointer: sdlValue.next) 61 | } 62 | 63 | internal init?(formatPointer: UnsafeMutablePointer?) { 64 | guard let formatPointer else { return nil } 65 | self.sdlPointer = formatPointer 66 | } 67 | 68 | public init(kind: Kind) throws { 69 | self.sdlPointer = try withThrowingSDL { 70 | SDL_AllocFormat(kind.rawValue) 71 | } 72 | self.needsFree = true 73 | } 74 | 75 | deinit { 76 | if needsFree { 77 | SDL_FreeFormat(sdlPointer) 78 | } 79 | } 80 | } 81 | 82 | // MARK: - Kind 83 | 84 | extension PixelFormat { 85 | public enum Kind { 86 | case unknown 87 | case index1lsb 88 | case index1msb 89 | case index4lsb 90 | case index4msb 91 | case index8 92 | case rgb332 93 | case rgb444 94 | case rgb555 95 | case bgr555 96 | case argb4444 97 | case rgba4444 98 | case abgr4444 99 | case bgra4444 100 | case argb1555 101 | case rgba5551 102 | case abgr1555 103 | case bgra5551 104 | case rgb565 105 | case bgr565 106 | case rgb24 107 | case bgr24 108 | case rgb888 109 | case rgbx8888 110 | case bgr888 111 | case bgrx8888 112 | case argb8888 113 | case rgba8888 114 | case abgr8888 115 | case bgra8888 116 | case argb2101010 117 | case yv12 118 | case iyuv 119 | case yuy2 120 | case uyvy 121 | case yvyu 122 | 123 | public static let rgba32: Self = .init(bigEndian: .rgba8888, littleEndian: .abgr8888) 124 | public static let argb32: Self = .init(bigEndian: .argb8888, littleEndian: .bgra8888) 125 | public static let bgra32: Self = .init(bigEndian: .bgra8888, littleEndian: .argb8888) 126 | public static let abgr32: Self = .init(bigEndian: .abgr8888, littleEndian: .rgba8888) 127 | 128 | public var rawValue: UInt32 { 129 | sdlPixelFormat.rawValue 130 | } 131 | 132 | public init(rawValue: UInt32) { 133 | self.init(sdlPixelFormat: SDL_PixelFormatEnum(rawValue: rawValue)) 134 | } 135 | 136 | // MARK: - Internal 137 | 138 | internal var sdlPixelFormat: SDL_PixelFormatEnum { 139 | switch self { 140 | case .unknown: return SDL_PIXELFORMAT_UNKNOWN 141 | case .index1lsb: return SDL_PIXELFORMAT_INDEX1LSB 142 | case .index1msb: return SDL_PIXELFORMAT_INDEX1MSB 143 | case .index4lsb: return SDL_PIXELFORMAT_INDEX4LSB 144 | case .index4msb: return SDL_PIXELFORMAT_INDEX4MSB 145 | case .index8: return SDL_PIXELFORMAT_INDEX8 146 | case .rgb332: return SDL_PIXELFORMAT_RGB332 147 | case .rgb444: return SDL_PIXELFORMAT_RGB444 148 | case .rgb555: return SDL_PIXELFORMAT_RGB555 149 | case .bgr555: return SDL_PIXELFORMAT_BGR555 150 | case .argb4444: return SDL_PIXELFORMAT_ARGB4444 151 | case .rgba4444: return SDL_PIXELFORMAT_RGBA4444 152 | case .abgr4444: return SDL_PIXELFORMAT_ABGR4444 153 | case .bgra4444: return SDL_PIXELFORMAT_BGRA4444 154 | case .argb1555: return SDL_PIXELFORMAT_ARGB1555 155 | case .rgba5551: return SDL_PIXELFORMAT_RGBA5551 156 | case .abgr1555: return SDL_PIXELFORMAT_ABGR1555 157 | case .bgra5551: return SDL_PIXELFORMAT_BGRA5551 158 | case .rgb565: return SDL_PIXELFORMAT_RGB565 159 | case .bgr565: return SDL_PIXELFORMAT_BGR565 160 | case .rgb24: return SDL_PIXELFORMAT_RGB24 161 | case .bgr24: return SDL_PIXELFORMAT_BGR24 162 | case .rgb888: return SDL_PIXELFORMAT_RGB888 163 | case .rgbx8888: return SDL_PIXELFORMAT_RGBX8888 164 | case .bgr888: return SDL_PIXELFORMAT_BGR888 165 | case .bgrx8888: return SDL_PIXELFORMAT_BGRX8888 166 | case .argb8888: return SDL_PIXELFORMAT_ARGB8888 167 | case .rgba8888: return SDL_PIXELFORMAT_RGBA8888 168 | case .abgr8888: return SDL_PIXELFORMAT_ABGR8888 169 | case .bgra8888: return SDL_PIXELFORMAT_BGRA8888 170 | case .argb2101010: return SDL_PIXELFORMAT_ARGB2101010 171 | case .yv12: return SDL_PIXELFORMAT_YV12 172 | case .iyuv: return SDL_PIXELFORMAT_IYUV 173 | case .yuy2: return SDL_PIXELFORMAT_YUY2 174 | case .uyvy: return SDL_PIXELFORMAT_UYVY 175 | case .yvyu: return SDL_PIXELFORMAT_YVYU 176 | } 177 | } 178 | 179 | internal init(sdlPixelFormat: SDL_PixelFormatEnum) { 180 | switch sdlPixelFormat { 181 | case SDL_PIXELFORMAT_INDEX1LSB: self = .index1lsb 182 | case SDL_PIXELFORMAT_INDEX1MSB: self = .index1msb 183 | case SDL_PIXELFORMAT_INDEX4LSB: self = .index4lsb 184 | case SDL_PIXELFORMAT_INDEX4MSB: self = .index4msb 185 | case SDL_PIXELFORMAT_INDEX8: self = .index8 186 | case SDL_PIXELFORMAT_RGB332: self = .rgb332 187 | case SDL_PIXELFORMAT_RGB444: self = .rgb444 188 | case SDL_PIXELFORMAT_RGB555: self = .rgb555 189 | case SDL_PIXELFORMAT_BGR555: self = .bgr555 190 | case SDL_PIXELFORMAT_ARGB4444: self = .argb4444 191 | case SDL_PIXELFORMAT_RGBA4444: self = .rgba4444 192 | case SDL_PIXELFORMAT_ABGR4444: self = .abgr4444 193 | case SDL_PIXELFORMAT_BGRA4444: self = .bgra4444 194 | case SDL_PIXELFORMAT_ARGB1555: self = .argb1555 195 | case SDL_PIXELFORMAT_RGBA5551: self = .rgba5551 196 | case SDL_PIXELFORMAT_ABGR1555: self = .abgr1555 197 | case SDL_PIXELFORMAT_BGRA5551: self = .bgra5551 198 | case SDL_PIXELFORMAT_RGB565: self = .rgb565 199 | case SDL_PIXELFORMAT_BGR565: self = .bgr565 200 | case SDL_PIXELFORMAT_RGB24: self = .rgb24 201 | case SDL_PIXELFORMAT_BGR24: self = .bgr24 202 | case SDL_PIXELFORMAT_RGB888: self = .rgb888 203 | case SDL_PIXELFORMAT_RGBX8888: self = .rgbx8888 204 | case SDL_PIXELFORMAT_BGR888: self = .bgr888 205 | case SDL_PIXELFORMAT_BGRX8888: self = .bgrx8888 206 | case SDL_PIXELFORMAT_ARGB8888: self = .argb8888 207 | case SDL_PIXELFORMAT_RGBA8888: self = .rgba8888 208 | case SDL_PIXELFORMAT_ABGR8888: self = .abgr8888 209 | case SDL_PIXELFORMAT_BGRA8888: self = .bgra8888 210 | case SDL_PIXELFORMAT_ARGB2101010: self = .argb2101010 211 | case SDL_PIXELFORMAT_YV12: self = .yv12 212 | case SDL_PIXELFORMAT_IYUV: self = .iyuv 213 | case SDL_PIXELFORMAT_YUY2: self = .yuy2 214 | case SDL_PIXELFORMAT_UYVY: self = .uyvy 215 | case SDL_PIXELFORMAT_YVYU: self = .yvyu 216 | default: self = .unknown 217 | } 218 | } 219 | 220 | internal init( 221 | bigEndian: @autoclosure () -> Self, 222 | littleEndian: @autoclosure () -> Self 223 | ) { 224 | if Endianness.current == .big { 225 | self = bigEndian() 226 | } else { 227 | self = littleEndian() 228 | } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Renderer.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 | @MainActor 24 | public final class Renderer { 25 | @usableFromInline 26 | internal let _sdlRendererPointer: OpaquePointer 27 | 28 | public init(window: Window, options: Options) throws { 29 | self._sdlRendererPointer = try withThrowingSDL { 30 | SDL_CreateRenderer(window._sdlWindowPtr, -1, options.rawValue) 31 | } 32 | } 33 | 34 | deinit { 35 | SDL_DestroyRenderer(_sdlRendererPointer) 36 | } 37 | } 38 | 39 | extension Renderer { 40 | @inlinable 41 | public func setColor(_ color: Color) { 42 | SDL_SetRenderDrawColor( 43 | _sdlRendererPointer, 44 | color.red, 45 | color.green, 46 | color.blue, 47 | color.alpha 48 | ) 49 | } 50 | 51 | @inlinable 52 | public func clear() throws { 53 | try withThrowingSDL { 54 | SDL_RenderClear(_sdlRendererPointer) 55 | } 56 | } 57 | 58 | @inlinable 59 | public func present() { 60 | SDL_RenderPresent(_sdlRendererPointer) 61 | } 62 | 63 | public func fillRect(_ rect: IRect) throws { 64 | try withThrowingSDL { 65 | withUnsafePointer(to: rect) { ptr in 66 | ptr.withMemoryRebound(to: SDL_Rect.self, capacity: 1) { sdlPtr in 67 | SDL_RenderFillRect(_sdlRendererPointer, sdlPtr) 68 | } 69 | } 70 | } 71 | } 72 | 73 | public func fillRect(_ rect: FRect) throws { 74 | try withThrowingSDL { 75 | withUnsafePointer(to: rect) { ptr in 76 | ptr.withMemoryRebound(to: SDL_FRect.self, capacity: 1) { sdlPtr in 77 | SDL_RenderFillRectF(_sdlRendererPointer, sdlPtr) 78 | } 79 | } 80 | } 81 | } 82 | 83 | public func renderCopy(_ texture: Texture, source: IRect? = nil, destination: IRect?) throws { 84 | try withThrowingSDL { 85 | withUnsafeOptionalSDLPointer(to: source, reboundTo: SDL_Rect.self) { srcPtr in 86 | withUnsafeOptionalSDLPointer(to: destination, reboundTo: SDL_Rect.self) { dstPtr in 87 | SDL_RenderCopy(_sdlRendererPointer, texture.sdlPointer, srcPtr, dstPtr) 88 | } 89 | } 90 | } 91 | } 92 | 93 | public func renderCopy( 94 | _ texture: Texture, 95 | source: __shared IRect? = nil, 96 | destination: __shared FRect? 97 | ) throws { 98 | try withThrowingSDL { 99 | withUnsafeOptionalSDLPointer(to: source, reboundTo: SDL_Rect.self) { srcPtr in 100 | withUnsafeOptionalSDLPointer(to: destination, reboundTo: SDL_FRect.self) { dstPtr in 101 | SDL_RenderCopyF(_sdlRendererPointer, texture.sdlPointer, srcPtr, dstPtr) 102 | } 103 | } 104 | } 105 | } 106 | 107 | public func renderCopy( 108 | _ texture: Texture, 109 | source: __shared IRect? = nil, 110 | destination: __shared FRect?, 111 | rotation: Angle, 112 | center: FVector2? = nil, 113 | flip: AxisSet = .none 114 | ) throws { 115 | try withThrowingSDL { 116 | withUnsafeOptionalSDLPointer(to: source, reboundTo: SDL_Rect.self) { srcPtr in 117 | withUnsafeOptionalSDLPointer(to: destination, reboundTo: SDL_FRect.self) { dstPtr in 118 | withUnsafeOptionalSDLPointer(to: center, reboundTo: SDL_FPoint.self) { centerPtr in 119 | SDL_RenderCopyExF( 120 | _sdlRendererPointer, 121 | texture.sdlPointer, 122 | srcPtr, 123 | dstPtr, 124 | rotation.degrees, 125 | centerPtr, 126 | flip.sdlRendererFlip 127 | ) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | // TODO: Move 136 | 137 | public struct AxisSet: OptionSet { 138 | public var rawValue: UInt8 139 | 140 | public init(rawValue: UInt8) { 141 | self.rawValue = rawValue 142 | } 143 | 144 | public static let none: Self = [] 145 | public static let horizontal: Self = .init(rawValue: 1) 146 | public static let vertical: Self = .init(rawValue: 2) 147 | 148 | internal var sdlRendererFlip: SDL_RendererFlip { 149 | .init(UInt32(rawValue)) 150 | } 151 | } 152 | 153 | // MARK: - Options 154 | 155 | extension Renderer { 156 | public struct Options: OptionSet, Sendable { 157 | public var rawValue: UInt32 158 | 159 | public init(rawValue: UInt32) { 160 | self.rawValue = rawValue 161 | } 162 | 163 | init(_ flags: SDL_RendererFlags) { 164 | self.rawValue = flags.rawValue 165 | } 166 | 167 | /// The renderer is a software fallback. 168 | public static let software: Self = .init(SDL_RENDERER_SOFTWARE) 169 | 170 | /// The renderer uses hardware acceleration. 171 | public static let accelerated: Self = .init(SDL_RENDERER_ACCELERATED) 172 | 173 | /// Present is synchronized with the refresh rate. 174 | public static let presentVSync: Self = .init(SDL_RENDERER_PRESENTVSYNC) 175 | 176 | /// The renderer supports rendering to texture. 177 | public static let targetTexture: Self = .init(SDL_RENDERER_TARGETTEXTURE) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Surface.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 | import SDL2Image 23 | 24 | public final class Surface { 25 | let sdlPointer: UnsafeMutablePointer 26 | 27 | init(sdlPointer: UnsafeMutablePointer) { 28 | self.sdlPointer = sdlPointer 29 | } 30 | 31 | deinit { 32 | SDL_FreeSurface(sdlPointer) 33 | } 34 | } 35 | 36 | extension Surface { 37 | public convenience init(path: String) throws { 38 | let pointer = try withThrowingSDL { 39 | path.withCString { 40 | IMG_Load($0) 41 | } 42 | } 43 | self.init(sdlPointer: pointer) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Texture.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 final class Texture { 24 | public struct Metadata { 25 | public var size: ISize2 26 | public var formatKind: PixelFormat.Kind 27 | public var accessRawValue: Int32 // TODO: Add actual 'access' type 28 | } 29 | 30 | let sdlPointer: OpaquePointer 31 | 32 | public var size: ISize2 { 33 | var size = ISize2() 34 | SDL_QueryTexture(sdlPointer, nil, nil, &size.width, &size.height) 35 | return size 36 | } 37 | 38 | public init(renderer: Renderer, surface: Surface) throws { 39 | let texturePointer = try withThrowingSDL { 40 | SDL_CreateTextureFromSurface(renderer._sdlRendererPointer, surface.sdlPointer) 41 | } 42 | self.sdlPointer = texturePointer 43 | // TODO: Format, Access 44 | } 45 | 46 | public func metadata() throws -> Metadata { 47 | var formatRawValue: UInt32 = 0 48 | var accessRawValue: Int32 = 0 49 | var width: Int32 = 0 50 | var height: Int32 = 0 51 | try withThrowingSDL { 52 | SDL_QueryTexture( 53 | sdlPointer, 54 | &formatRawValue, 55 | &accessRawValue, 56 | &width, 57 | &height 58 | ) 59 | } 60 | return Metadata( 61 | size: ISize2(width: width, height: height), 62 | formatKind: PixelFormat.Kind(rawValue: formatRawValue), 63 | accessRawValue: accessRawValue 64 | ) 65 | } 66 | 67 | deinit { 68 | SDL_DestroyTexture(sdlPointer) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Lark/Rendering/Window.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 | @MainActor 24 | public final class Window { 25 | internal let _sdlWindowPtr: OpaquePointer 26 | 27 | public init( 28 | title: String, 29 | x: Int32, 30 | y: Int32, 31 | w: Int32, 32 | h: Int32, 33 | options: Options = [] 34 | ) throws { 35 | self._sdlWindowPtr = try withThrowingSDL { 36 | title.withCString { ptr in 37 | SDL_CreateWindow( 38 | ptr, 39 | x, 40 | y, 41 | w, 42 | h, 43 | options.rawValue 44 | ) 45 | } 46 | } 47 | } 48 | 49 | public init( 50 | title: String, 51 | position: Position, 52 | size: Size2, 53 | options: Options = [] 54 | ) throws { 55 | let mask = position.windowMask 56 | self._sdlWindowPtr = try withThrowingSDL { 57 | title.withCString { ptr in 58 | SDL_CreateWindow( 59 | ptr, 60 | Int32(bitPattern: mask), 61 | Int32(bitPattern: mask), 62 | Int32(size.width), 63 | Int32(size.height), 64 | options.rawValue 65 | ) 66 | } 67 | } 68 | } 69 | 70 | deinit { 71 | SDL_DestroyWindow(_sdlWindowPtr) 72 | } 73 | 74 | public func setFullScreen(options: Window.Options = [.fullscreen]) throws { 75 | try withThrowingSDL { 76 | SDL_SetWindowFullscreen(_sdlWindowPtr, options.rawValue) 77 | } 78 | } 79 | } 80 | 81 | extension Window { 82 | public enum Position { 83 | case centered 84 | case undefined 85 | 86 | var windowMask: UInt32 { 87 | switch self { 88 | case .centered: return SDL_WINDOWPOS_CENTERED_MASK 89 | case .undefined: return SDL_WINDOWPOS_UNDEFINED_MASK 90 | } 91 | } 92 | } 93 | 94 | public struct Options: OptionSet { 95 | public var rawValue: UInt32 96 | 97 | public init(rawValue: UInt32) { 98 | self.rawValue = rawValue 99 | } 100 | 101 | public static let fullscreen: Self = .init(rawValue: SDL_WINDOW_FULLSCREEN.rawValue) 102 | public static let fullscreenDesktop: Self = .init( 103 | rawValue: SDL_WINDOW_FULLSCREEN_DESKTOP.rawValue) 104 | public static let openGL: Self = .init(rawValue: SDL_WINDOW_OPENGL.rawValue) 105 | public static let metal: Self = .init(rawValue: SDL_WINDOW_METAL.rawValue) 106 | public static let vulkan: Self = .init(rawValue: SDL_WINDOW_VULKAN.rawValue) 107 | public static let shown: Self = .init(rawValue: SDL_WINDOW_SHOWN.rawValue) 108 | public static let hidden: Self = .init(rawValue: SDL_WINDOW_HIDDEN.rawValue) 109 | public static let borderless: Self = .init(rawValue: SDL_WINDOW_BORDERLESS.rawValue) 110 | public static let resizable: Self = .init(rawValue: SDL_WINDOW_RESIZABLE.rawValue) 111 | public static let minimized: Self = .init(rawValue: SDL_WINDOW_MAXIMIZED.rawValue) 112 | public static let mouseGrabbed: Self = .init(rawValue: SDL_WINDOW_MOUSE_GRABBED.rawValue) 113 | public static let inputFocus: Self = .init(rawValue: SDL_WINDOW_INPUT_FOCUS.rawValue) 114 | public static let mouseFocus: Self = .init(rawValue: SDL_WINDOW_MOUSE_FOCUS.rawValue) 115 | public static let foreign: Self = .init(rawValue: SDL_WINDOW_FOREIGN.rawValue) 116 | public static let allowHighDPI: Self = .init(rawValue: SDL_WINDOW_ALLOW_HIGHDPI.rawValue) 117 | public static let mouseCapture: Self = .init(rawValue: SDL_WINDOW_MOUSE_CAPTURE.rawValue) 118 | public static let alwaysOnTop: Self = .init(rawValue: SDL_WINDOW_ALWAYS_ON_TOP.rawValue) 119 | public static let skipTaskBar: Self = .init(rawValue: SDL_WINDOW_SKIP_TASKBAR.rawValue) 120 | public static let utility: Self = .init(rawValue: SDL_WINDOW_UTILITY.rawValue) 121 | public static let tooltip: Self = .init(rawValue: SDL_WINDOW_TOOLTIP.rawValue) 122 | public static let popupMenu: Self = .init(rawValue: SDL_WINDOW_POPUP_MENU.rawValue) 123 | public static let keyboardGrabbed: Self = .init(rawValue: SDL_WINDOW_KEYBOARD_GRABBED.rawValue) 124 | public static let inputGrabbed: Self = .init(rawValue: SDL_WINDOW_INPUT_GRABBED.rawValue) 125 | 126 | /* 127 | /** < fullscreen window */ 128 | public var SDL_WINDOW_FULLSCREEN: SDL_WindowFlags { get } 129 | /** < window usable with OpenGL context */ 130 | public var SDL_WINDOW_OPENGL: SDL_WindowFlags { get } 131 | /** < window is visible */ 132 | public var SDL_WINDOW_SHOWN: SDL_WindowFlags { get } 133 | /** < window is not visible */ 134 | public var SDL_WINDOW_HIDDEN: SDL_WindowFlags { get } 135 | /** < no window decoration */ 136 | public var SDL_WINDOW_BORDERLESS: SDL_WindowFlags { get } 137 | /** < window can be resized */ 138 | public var SDL_WINDOW_RESIZABLE: SDL_WindowFlags { get } 139 | /** < window is minimized */ 140 | public var SDL_WINDOW_MINIMIZED: SDL_WindowFlags { get } 141 | /** < window is maximized */ 142 | public var SDL_WINDOW_MAXIMIZED: SDL_WindowFlags { get } 143 | /** < window has grabbed mouse input */ 144 | public var SDL_WINDOW_MOUSE_GRABBED: SDL_WindowFlags { get } 145 | /** < window has input focus */ 146 | public var SDL_WINDOW_INPUT_FOCUS: SDL_WindowFlags { get } 147 | /** < window has mouse focus */ 148 | public var SDL_WINDOW_MOUSE_FOCUS: SDL_WindowFlags { get } 149 | public var SDL_WINDOW_FULLSCREEN_DESKTOP: SDL_WindowFlags { get } 150 | /** < window not created by SDL */ 151 | public var SDL_WINDOW_FOREIGN: SDL_WindowFlags { get } 152 | /** < window should be created in high-DPI mode if supported. 153 | On macOS NSHighResolutionCapable must be set true in the 154 | application's Info.plist for this to have any effect. */ 155 | public var SDL_WINDOW_ALLOW_HIGHDPI: SDL_WindowFlags { get } 156 | /** < window has mouse captured (unrelated to MOUSE_GRABBED) */ 157 | public var SDL_WINDOW_MOUSE_CAPTURE: SDL_WindowFlags { get } 158 | /** < window should always be above others */ 159 | public var SDL_WINDOW_ALWAYS_ON_TOP: SDL_WindowFlags { get } 160 | /** < window should not be added to the taskbar */ 161 | public var SDL_WINDOW_SKIP_TASKBAR: SDL_WindowFlags { get } 162 | /** < window should be treated as a utility window */ 163 | public var SDL_WINDOW_UTILITY: SDL_WindowFlags { get } 164 | /** < window should be treated as a tooltip */ 165 | public var SDL_WINDOW_TOOLTIP: SDL_WindowFlags { get } 166 | /** < window should be treated as a popup menu */ 167 | public var SDL_WINDOW_POPUP_MENU: SDL_WindowFlags { get } 168 | /** < window has grabbed keyboard input */ 169 | public var SDL_WINDOW_KEYBOARD_GRABBED: SDL_WindowFlags { get } 170 | /** < window usable for Vulkan surface */ 171 | public var SDL_WINDOW_VULKAN: SDL_WindowFlags { get } 172 | /** < window usable for Metal view */ 173 | public var SDL_WINDOW_METAL: SDL_WindowFlags { get } 174 | /** < equivalent to SDL_WINDOW_MOUSE_GRABBED for compatibility */ 175 | public var SDL_WINDOW_INPUT_GRABBED: SDL_WindowFlags { get } 176 | */ 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Sources/Lark/Time/GetTicks.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 Ticks: Sendable { 24 | @usableFromInline 25 | let getTicks: @Sendable () -> Duration 26 | 27 | @inlinable 28 | internal init(getTicks: @escaping @Sendable () -> Duration) { 29 | self.getTicks = getTicks 30 | } 31 | 32 | @inlinable 33 | internal static func sdl32() -> Self { 34 | Self { .milliseconds(SDL_GetTicks()) } 35 | } 36 | 37 | @inlinable 38 | internal static func sdl64() -> Self { 39 | Self { .milliseconds(SDL_GetTicks64()) } 40 | } 41 | 42 | @inlinable 43 | public func callAsFunction() -> Duration { 44 | getTicks() 45 | } 46 | 47 | @inlinable 48 | public func get() -> Duration { 49 | getTicks() 50 | } 51 | } 52 | 53 | private enum GetTicks32: EnvironmentKey { 54 | static let defaultValue: Ticks = .sdl32() 55 | } 56 | 57 | private enum GetTicks64: EnvironmentKey { 58 | static let defaultValue: Ticks = .sdl64() 59 | } 60 | 61 | extension EnvironmentValues { 62 | public var getTicks32: Ticks { 63 | get { self[GetTicks32.self] } 64 | set { self[GetTicks32.self] = newValue } 65 | } 66 | 67 | public var getTicks64: Ticks { 68 | get { self[GetTicks64.self] } 69 | set { self[GetTicks64.self] = newValue } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Lark/Time/LarkClock.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 | import XPC 23 | 24 | public struct LarkClock: Clock { 25 | public typealias Duration = LarkDuration 26 | 27 | public typealias Sleep = @Sendable ( 28 | _ deadline: Instant, 29 | _ tolerance: LarkDuration? 30 | ) async throws -> Void 31 | 32 | public struct Instant: InstantProtocol { 33 | public typealias Getter = @Sendable () -> Instant 34 | public typealias Duration = LarkDuration 35 | 36 | public let offset: LarkDuration 37 | 38 | public init(offset: LarkDuration) { 39 | self.offset = offset 40 | } 41 | 42 | public func advanced(by duration: LarkDuration) -> Self { 43 | Instant(offset: offset + duration) 44 | } 45 | 46 | public func duration(to other: Self) -> LarkDuration { 47 | other.offset - offset 48 | } 49 | 50 | public static func < (lhs: LarkClock.Instant, rhs: LarkClock.Instant) -> Bool { 51 | lhs.offset < rhs.offset 52 | } 53 | } 54 | 55 | private let _now: Instant.Getter 56 | private let _sleep: Sleep 57 | 58 | public init(now: @escaping Instant.Getter, sleep: @escaping Sleep) { 59 | self._now = now 60 | self._sleep = sleep 61 | } 62 | 63 | public static let live: Self = { 64 | let now: Instant.Getter = { Instant(offset: .milliseconds(SDL_GetTicks64())) } 65 | return Self( 66 | now: now, 67 | sleep: { deadline, _ in SDL_Delay(UInt32(now().duration(to: deadline).milliseconds)) } 68 | ) 69 | }() 70 | 71 | public var now: Instant { _now() } 72 | 73 | public var minimumResolution: LarkDuration { .zero } 74 | 75 | public func sleep(until deadline: Instant, tolerance: LarkDuration? = nil) async throws {} 76 | } 77 | -------------------------------------------------------------------------------- /Sources/Lark/Time/LarkDuration.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 LarkDuration: Hashable, Sendable { 22 | public var seconds: Float 23 | 24 | public var milliseconds: Float { seconds * 1000 } 25 | 26 | public init(seconds: Float) { 27 | self.seconds = seconds 28 | } 29 | 30 | public var swiftDuration: Duration { 31 | .seconds(Double(seconds)) 32 | } 33 | } 34 | 35 | extension LarkDuration { 36 | @inlinable 37 | public static func seconds(_ seconds: some BinaryInteger) -> Self { 38 | Self(seconds: Float(seconds)) 39 | } 40 | 41 | @inlinable 42 | public static func seconds(_ seconds: some BinaryFloatingPoint) -> Self { 43 | Self(seconds: Float(seconds)) 44 | } 45 | 46 | @inlinable 47 | public static func milliseconds(_ milliseconds: some BinaryInteger) -> Self { 48 | Self(seconds: Float(milliseconds) * 0.001) 49 | } 50 | 51 | @inlinable 52 | public static func milliseconds(_ milliseconds: some BinaryFloatingPoint) -> Self { 53 | Self(seconds: Float(milliseconds) * 0.001) 54 | } 55 | 56 | public static let fps120: Self = .init(seconds: 1 / 120) 57 | public static let fps60: Self = .init(seconds: 1 / 60) 58 | public static let fps30: Self = .init(seconds: 1 / 30) 59 | } 60 | 61 | extension LarkDuration: Comparable { 62 | public static func < (lhs: LarkDuration, rhs: LarkDuration) -> Bool { 63 | lhs.seconds < rhs.seconds 64 | } 65 | } 66 | 67 | extension LarkDuration: AdditiveArithmetic { 68 | public static var zero: LarkDuration { .init(seconds: 0) } 69 | 70 | public static func - (lhs: LarkDuration, rhs: LarkDuration) -> LarkDuration { 71 | .init(seconds: lhs.seconds - rhs.seconds) 72 | } 73 | 74 | public static func + (lhs: LarkDuration, rhs: LarkDuration) -> LarkDuration { 75 | .init(seconds: lhs.seconds + rhs.seconds) 76 | } 77 | } 78 | 79 | extension LarkDuration: DurationProtocol { 80 | public static func / (lhs: LarkDuration, rhs: Int) -> LarkDuration { 81 | .init(seconds: lhs.seconds / Float(rhs)) 82 | } 83 | 84 | public static func * (lhs: LarkDuration, rhs: Int) -> LarkDuration { 85 | .init(seconds: lhs.seconds * Float(rhs)) 86 | } 87 | 88 | public static func / (lhs: LarkDuration, rhs: LarkDuration) -> Double { 89 | Double(lhs.seconds) / Double(rhs.seconds) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/ActorIsolated.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 actor ActorIsolated { 22 | public var value: Value 23 | 24 | @inlinable 25 | public init(_ value: Value) { 26 | self.value = value 27 | } 28 | 29 | @inlinable 30 | public func withValue(_ operation: @Sendable (inout Value) -> T) async -> T { 31 | operation(&value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/Bimap.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 Bimap { 22 | @usableFromInline 23 | internal var byKey: [Key: Value] = [:] 24 | 25 | @usableFromInline 26 | internal var byValue: [Value: Key] = [:] 27 | 28 | public init() {} 29 | 30 | @inlinable 31 | public subscript(key key: Key) -> Value? { 32 | _read { 33 | yield byKey[key] 34 | } 35 | set(newValue) { 36 | if let newValue { 37 | setValue(newValue, forKey: key) 38 | } else { 39 | removeValue(forKey: key) 40 | } 41 | } 42 | } 43 | 44 | @inlinable 45 | public subscript(value value: Value) -> Key? { 46 | _read { 47 | yield byValue[value] 48 | } 49 | set(newKey) { 50 | if let newKey { 51 | setValue(value, forKey: newKey) 52 | } else { 53 | removeKey(forValue: value) 54 | } 55 | } 56 | } 57 | 58 | @inlinable 59 | @discardableResult 60 | public mutating func removeValue(forKey key: Key) -> Value? { 61 | guard let value = byKey.removeValue(forKey: key) else { return nil } 62 | byValue.removeValue(forKey: value) 63 | return value 64 | } 65 | 66 | @inlinable 67 | @discardableResult 68 | public mutating func removeKey(forValue value: Value) -> Key? { 69 | guard let key = byValue.removeValue(forKey: value) else { return nil } 70 | byKey.removeValue(forKey: key) 71 | return key 72 | } 73 | 74 | @inlinable 75 | public mutating func setValue(_ value: Value, forKey key: Key) { 76 | byKey[key] = value 77 | byValue[value] = key 78 | } 79 | } 80 | 81 | extension Bimap: Equatable, Hashable {} 82 | 83 | extension Bimap: Sendable where Key: Sendable, Value: Sendable {} 84 | 85 | extension Bimap: Sequence, Collection { 86 | public typealias Index = Dictionary.Index 87 | public typealias Element = Dictionary.Element 88 | 89 | @inlinable 90 | public var count: Int { 91 | byKey.count 92 | } 93 | 94 | @inlinable 95 | public var startIndex: Dictionary.Index { 96 | byKey.startIndex 97 | } 98 | 99 | @inlinable 100 | public var endIndex: Dictionary.Index { 101 | byKey.endIndex 102 | } 103 | 104 | @inlinable 105 | public subscript(position: Dictionary.Index) -> Dictionary.Element { 106 | _read { 107 | yield byKey[position] 108 | } 109 | } 110 | 111 | @inlinable 112 | public func index(after i: Dictionary.Index) -> Dictionary.Index { 113 | byKey.index(after: i) 114 | } 115 | } 116 | 117 | extension Bimap: ExpressibleByDictionaryLiteral { 118 | public init(dictionaryLiteral elements: (Key, Value)...) { 119 | self.byKey = .init(minimumCapacity: elements.count) 120 | self.byValue = .init(minimumCapacity: elements.count) 121 | 122 | for (key, value) in elements { 123 | setValue(value, forKey: key) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/Configurable.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 typealias Configure = (inout Value) -> Void 22 | 23 | public protocol Configurable {} 24 | 25 | extension Configurable { 26 | public __consuming func with(_ configuration: Configure) -> Self { 27 | configure(self, with: configuration) 28 | } 29 | } 30 | 31 | extension Configurable where Self: EmptyInitializable { 32 | public init(configure: Configure) { 33 | self.init() 34 | configure(&self) 35 | } 36 | } 37 | 38 | public func configure( 39 | _ value: __owned Value, 40 | with configure: Configure 41 | ) -> Value { 42 | var value = /* move */ value 43 | configure(&value) 44 | return value 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/EmptyInitializable.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 EmptyInitializable { 22 | init() 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/Endianness.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 Endianness { 22 | case big, little 23 | 24 | public static let current: Endianness = { 25 | if 42.bigEndian == 42 { 26 | return .big 27 | } else { 28 | return .little 29 | } 30 | }() 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/IncrementingNumberGenerator.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 IncrementingNumberGenerator: RandomNumberGenerator { 22 | public var currentValue: UInt64 23 | 24 | public init(initialValue: UInt64 = 0) { 25 | self.currentValue = initialValue 26 | } 27 | 28 | public mutating func next() -> UInt64 { 29 | defer { currentValue &+= 1 } 30 | return currentValue 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/LockedIncrementer.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 Lock 22 | 23 | @usableFromInline 24 | internal struct LockedIncrementer { 25 | @usableFromInline 26 | let value: Lock 27 | 28 | @usableFromInline 29 | init() { 30 | self.value = .init(unchecked: .zero) 31 | } 32 | 33 | @inlinable 34 | func next() -> Value { 35 | value.withLock { value in 36 | defer { value += 1 } 37 | return value 38 | } 39 | } 40 | } 41 | 42 | extension LockedIncrementer: Sendable where Value: Sendable {} 43 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/Optional+OrThrow.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 | extension Optional { 22 | public struct UnwrapError: Error {} 23 | 24 | public func orThrow(_ error: @autoclosure () -> Error) throws -> Wrapped { 25 | guard let self else { throw error() } 26 | return self 27 | } 28 | 29 | public func orThrow() throws -> Wrapped { 30 | try orThrow(UnwrapError()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/Proxy.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 | @dynamicMemberLookup 23 | public struct Proxy { 24 | private let get: () -> Value 25 | private let set: (Value) -> Void 26 | 27 | public var wrappedValue: Value { 28 | get { get() } 29 | nonmutating set { set(newValue) } 30 | } 31 | 32 | public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) { 33 | self.get = get 34 | self.set = set 35 | } 36 | 37 | public init( 38 | _ strongRoot: Root, 39 | _ keyPath: ReferenceWritableKeyPath 40 | ) { 41 | self.init( 42 | get: { strongRoot[keyPath: keyPath] }, 43 | set: { strongRoot[keyPath: keyPath] = $0 } 44 | ) 45 | } 46 | 47 | public init( 48 | weak root: Root, 49 | _ keyPath: ReferenceWritableKeyPath 50 | ) { 51 | var fallback = root[keyPath: keyPath] 52 | self.init( 53 | get: { [weak root] in root?[keyPath: keyPath] ?? fallback }, 54 | set: { [weak root] in 55 | root?[keyPath: keyPath] = $0 56 | fallback = $0 57 | } 58 | ) 59 | } 60 | 61 | public subscript( 62 | dynamicMember keyPath: WritableKeyPath 63 | ) -> Proxy { 64 | Proxy( 65 | get: { wrappedValue[keyPath: keyPath] }, 66 | set: { wrappedValue[keyPath: keyPath] = $0 } 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/SDLPointers.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 | @inlinable 22 | public func withUnsafeSDLPointer( 23 | to value: Value, 24 | reboundTo: SDLType.Type, 25 | capacity: Int = 1, 26 | _ work: (UnsafePointer) throws -> ResultType 27 | ) rethrows -> ResultType { 28 | try withUnsafePointer(to: value) { ptr in 29 | try ptr.withMemoryRebound(to: SDLType.self, capacity: capacity) { sdlPtr in 30 | try work(sdlPtr) 31 | } 32 | } 33 | } 34 | 35 | @inlinable 36 | public func withUnsafeOptionalSDLPointer( 37 | to value: Value?, 38 | reboundTo: SDLType.Type, 39 | capacity: Int = 1, 40 | _ work: (UnsafePointer?) throws -> ResultType 41 | ) rethrows -> ResultType { 42 | try value.map { value in 43 | try withUnsafeSDLPointer(to: value, reboundTo: SDLType.self, capacity: capacity) { sdlPtr in 44 | try work(sdlPtr) 45 | } 46 | } ?? work(nil) 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Lark/Utilities/UncheckedSendable.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 UncheckedSendable: @unchecked Sendable { 22 | @usableFromInline 23 | internal var value: Value 24 | 25 | @inlinable 26 | init(_ value: Value) { 27 | self.value = value 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/LarkExample/Entities.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 Lark 22 | 23 | final class TankEntity: Entity { 24 | let id: EntityID 25 | let registry: Registry 26 | 27 | @ComponentProxy var transform: TransformComponent = .init( 28 | position: .init(10, 10), 29 | scale: .unit * 3, 30 | rotation: .degrees(45) 31 | ) 32 | @ComponentProxy var rigidBody: RigidBodyComponent = .init(velocity: .init(1, 0)) 33 | @ComponentProxy var sprite: SpriteComponent = .init(textureAssetID: Asset.tankSprite.id) 34 | 35 | init(id: EntityID, registry: Registry) { 36 | self.id = id 37 | self.registry = registry 38 | } 39 | } 40 | 41 | final class TruckEntity: Entity { 42 | let id: EntityID 43 | let registry: Registry 44 | 45 | @ComponentProxy var transform: TransformComponent = .init(position: .init(10, 10)) 46 | @ComponentProxy var rigidBody: RigidBodyComponent = .init(velocity: .init(0, 0.5)) 47 | @ComponentProxy var sprite: SpriteComponent = .init(textureAssetID: Asset.truckSprite.id) 48 | 49 | init(id: EntityID, registry: Registry) { 50 | self.id = id 51 | self.registry = registry 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/LarkExample/Run.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 | import Lark 23 | import Logging 24 | import SDL2 25 | 26 | extension Asset where AssetType == Texture { 27 | static var tankSprite: Asset { 28 | Asset(id: "tankSprite", path: "/assets/images/tank-tiger-right.png") 29 | } 30 | 31 | static var truckSprite: Asset { 32 | Asset(id: "truckSprite", path: "/assets/images/truck-ford-down.png") 33 | } 34 | } 35 | 36 | @MainActor 37 | final class ExampleGame: GameProtocol { 38 | var isRunning: Bool = true 39 | 40 | var window: Window 41 | var renderer: Renderer 42 | var windowSize: ISize2 43 | 44 | @Environment(\.logger) private var logger 45 | @Environment(\.events) private var events 46 | 47 | // let registry: Registry 48 | // let assetStore: AssetStore 49 | // 50 | var activeScene: (any Scene)? 51 | 52 | init(registry: Registry, assetStore: AssetStore) throws { 53 | self.windowSize = ISize2(width: 800, height: 600) 54 | self.window = try Window( 55 | title: "Hi There!", 56 | position: .centered, 57 | size: windowSize, 58 | options: [.openGL] 59 | ) 60 | self.renderer = try Renderer( 61 | window: window, 62 | options: [.accelerated, .presentVSync, .targetTexture] 63 | ) 64 | registry.addSystem(MovementSystem()) 65 | registry.addSystem(RenderingSystem(renderer: renderer, assetStore: assetStore)) 66 | 67 | self.activeScene = try TestScene( 68 | assetStore: assetStore, 69 | renderer: renderer, 70 | registry: registry, 71 | isRunning: Proxy(weak: self, \.isRunning) 72 | ) 73 | } 74 | } 75 | 76 | @MainActor 77 | @main 78 | enum Run { 79 | static func main() async throws { 80 | try EnvironmentValues.withValues { 81 | $0.logger.logLevel = .info 82 | $0.resourcePath = Bundle.module.resourcePath 83 | } perform: { 84 | let engine = try Engine() 85 | let assetStore = AssetStore() 86 | try engine.run(ExampleGame(registry: engine.registry, assetStore: assetStore)) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/LarkExample/Scenes/Jungle.swift: -------------------------------------------------------------------------------- 1 | import Lark 2 | 3 | final class JungleTilemap: Entity { 4 | var id: EntityID 5 | var registry: Registry 6 | 7 | @ComponentProxy var sprite: SpriteComponent 8 | 9 | init(id: EntityID, registry: Registry) { 10 | self.id = id 11 | self.registry = registry 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/LarkExample/Scenes/TestScene.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 Lark 22 | 23 | final class TestScene: Scene { 24 | let tank: TankEntity 25 | let truck: TruckEntity 26 | let assetStore: AssetStore 27 | 28 | @Proxy var isRunning: Bool 29 | 30 | @Environment(\.events) private var events 31 | 32 | init( 33 | assetStore: AssetStore, 34 | renderer: Renderer, 35 | registry: Registry, 36 | isRunning: Proxy 37 | ) throws { 38 | self.assetStore = assetStore 39 | 40 | self._isRunning = isRunning 41 | try assetStore.addTexture(for: .tankSprite, with: renderer) 42 | try assetStore.addTexture(for: .truckSprite, with: renderer) 43 | 44 | 45 | self.tank = try registry.createEntity(TankEntity.self) 46 | self.truck = try registry.createEntity(TruckEntity.self) 47 | } 48 | 49 | deinit { 50 | // TODO: - isolated deinit 51 | Task { @MainActor [assetStore] in 52 | assetStore.clear(keepingCapacity: true) 53 | } 54 | } 55 | 56 | func update(deltaTime: LarkDuration) throws { 57 | for event in events() { 58 | switch event.kind { 59 | case .quit: 60 | isRunning = false 61 | case let .keyUp(info) where info.keyCode == .escape: 62 | isRunning = false 63 | default: 64 | break 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/LarkExample/assets/fonts/arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/fonts/arial.ttf -------------------------------------------------------------------------------- /Sources/LarkExample/assets/fonts/charriot.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/fonts/charriot.ttf -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/bullet.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/chopper-spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/chopper-spritesheet.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/chopper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/chopper.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/landing-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/landing-base.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/radar.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/takeoff-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/takeoff-base.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-panther-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-panther-down.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-panther-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-panther-left.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-panther-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-panther-right.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-panther-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-panther-up.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-tiger-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-tiger-down.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-tiger-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-tiger-left.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-tiger-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-tiger-right.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tank-tiger-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tank-tiger-up.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/tree.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/truck-ford-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/truck-ford-down.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/truck-ford-killed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/truck-ford-killed.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/truck-ford-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/truck-ford-left.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/truck-ford-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/truck-ford-right.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/images/truck-ford-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/images/truck-ford-up.png -------------------------------------------------------------------------------- /Sources/LarkExample/assets/sounds/helicopter.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/sounds/helicopter.wav -------------------------------------------------------------------------------- /Sources/LarkExample/assets/tilemaps/jungle.map: -------------------------------------------------------------------------------- 1 | 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 2 | 21,21,21,17,18,21,21,21,21,21,21,17,13,13,13,13,13,13,13,13,13,13,18,21,21 3 | 21,21,21,16,19,21,21,21,21,21,21,11,25,26,08,25,26,15,09,10,08,08,14,18,21 4 | 21,21,21,21,21,21,21,21,21,21,21,11,08,15,09,09,09,19,21,11,08,08,08,22,21 5 | 21,21,21,21,21,21,21,21,21,21,21,11,08,14,18,21,21,21,21,11,08,08,08,22,21 6 | 21,21,21,21,21,21,17,13,18,17,13,12,25,01,22,21,21,21,21,16,09,09,09,19,21 7 | 21,21,21,21,21,21,11,08,22,11,08,02,25,07,14,13,18,21,21,21,17,13,13,18,21 8 | 21,17,13,13,13,13,12,08,14,12,00,15,09,10,07,02,14,13,18,21,11,08,08,22,21 9 | 21,11,15,09,09,09,10,25,07,00,08,22,21,16,10,04,08,08,22,21,11,08,08,22,21 10 | 21,11,22,21,21,21,16,10,07,00,00,14,13,13,12,08,15,09,19,21,16,09,09,19,21 11 | 21,11,22,21,21,21,21,11,28,27,04,20,01,06,08,15,19,21,21,21,17,13,13,13,18 12 | 21,11,22,21,21,21,21,16,09,10,08,07,00,00,03,14,13,13,13,13,12,20,01,02,22 13 | 21,11,22,21,21,21,21,21,21,11,08,00,00,00,04,08,08,08,08,08,08,07,00,03,22 14 | 21,11,22,21,21,21,21,21,21,16,09,09,09,09,09,09,10,15,09,09,10,06,05,04,22 15 | 17,12,14,13,18,21,21,21,21,21,21,21,21,21,21,21,16,19,21,21,16,09,09,09,19 16 | 11,03,07,00,22,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 17 | 11,03,07,00,22,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 18 | 11,04,06,00,22,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 19 | 16,09,09,09,19,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 20 | 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21 -------------------------------------------------------------------------------- /Sources/LarkExample/assets/tilemaps/jungle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junebash/swift-lark/503a6c91dafdf8fb1db504efaff284ffaaeea122/Sources/LarkExample/assets/tilemaps/jungle.png -------------------------------------------------------------------------------- /Sources/SDL2/Export.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 | @_exported import CSDL2 22 | -------------------------------------------------------------------------------- /Sources/SDL2Image/SDL2Image.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 | @_exported import CSDL2Image 22 | import SDL2 23 | 24 | // public let IMG_INIT_ALL: Int32 = .init( 25 | // IMG_INIT_JPG.rawValue 26 | // | IMG_INIT_PNG.rawValue 27 | // | IMG_INIT_TIF.rawValue 28 | // | IMG_INIT_WEBP.rawValue 29 | // ) 30 | 31 | extension SDL_Surface { 32 | /// https://sdl.beuc.net/sdl.wiki/Pixel_Access 33 | @inlinable 34 | public func getPixel(x: Int, y: Int) -> UInt32 { 35 | let bpp = format.pointee.BytesPerPixel 36 | let p = pixels.advanced(by: y * Int(pitch) + x * Int(bpp)) 37 | 38 | switch bpp { 39 | case 1: 40 | return UInt32(p.assumingMemoryBound(to: Uint8.self).pointee) 41 | 42 | case 2: 43 | return UInt32(p.assumingMemoryBound(to: Uint16.self).pointee) 44 | 45 | case 3: 46 | let mp = p.assumingMemoryBound(to: UInt32.self) 47 | if SDL_BYTEORDER == BIG_ENDIAN { 48 | return mp[0] << 16 | mp[1] << 8 | mp[2] 49 | } else { 50 | return mp[0] | mp[1] << 8 | mp[2] << 16 51 | } 52 | 53 | case 4: 54 | return p.assumingMemoryBound(to: UInt32.self).pointee 55 | 56 | default: 57 | assertionFailure("Unexpected bytes per pixel: \(bpp)") 58 | return 0 59 | } 60 | } 61 | 62 | @inlinable 63 | public func getPixelRGBA(x: Int, y: Int) -> (UInt8, UInt8, UInt8, UInt8) { 64 | let pixel = getPixel(x: x, y: y) 65 | var r: UInt8 = 0 66 | var g: UInt8 = 0 67 | var b: UInt8 = 0 68 | var a: UInt8 = 0 69 | SDL_GetRGBA(pixel, format, &r, &g, &b, &a) 70 | return (r, g, b, a) 71 | } 72 | 73 | @inlinable 74 | public func getPixelRGB(x: Int, y: Int) -> (UInt8, UInt8, UInt8) { 75 | let pixel = getPixel(x: x, y: y) 76 | var r: UInt8 = 0 77 | var g: UInt8 = 0 78 | var b: UInt8 = 0 79 | SDL_GetRGB(pixel, format, &r, &g, &b) 80 | return (r, g, b) 81 | } 82 | 83 | @inlinable public var pixelBuffer: UnsafeMutableRawBufferPointer { 84 | let count = Int(Int32(pitch) * h * Int32(format.pointee.BytesPerPixel)) 85 | return UnsafeMutableRawBufferPointer( 86 | start: pixels, 87 | count: count 88 | ) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/LarkTests/ColorTests.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 XCTest 22 | 23 | @testable import Lark 24 | 25 | final class ColorTests: XCTestCase { 26 | func test_normalizeComponent() { 27 | XCTAssertEqual(Color._normalizeComponent(1), 255) 28 | XCTAssertEqual(Color._normalizeComponent(84849), 255) 29 | XCTAssertEqual(Color._normalizeComponent(0), 0) 30 | XCTAssertEqual(Color._normalizeComponent(-937458), 0) 31 | XCTAssertEqual(Color._normalizeComponent(0.5), 127) 32 | } 33 | 34 | func test_denormalizeComponent() { 35 | XCTAssertEqual(Color._denormalizeComponent(255), 1) 36 | XCTAssertEqual(Color._denormalizeComponent(0), 0) 37 | XCTAssertEqual(Color._denormalizeComponent(127), 0.5, accuracy: 0.01) 38 | } 39 | } 40 | --------------------------------------------------------------------------------