diff --git a/Package.swift b/Package.swift index 5b2d78785..7609df785 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,7 @@ let package = Package( .byName( name: "Spine", condition: .when(platforms: [ - .iOS, + .iOS ]) ), "SpineCppLite", @@ -38,7 +38,7 @@ let package = Package( .target( name: "Spine", dependencies: [ - "SpineCppLite", "SpineShadersStructs" + "SpineCppLite", "SpineShadersStructs", ], path: "spine-ios/Sources/Spine" ), @@ -46,13 +46,13 @@ let package = Package( name: "SpineCppLite", path: "spine-ios/Sources/SpineCppLite", linkerSettings: [ - .linkedLibrary("c++"), + .linkedLibrary("c++") ] ), .systemLibrary( name: "SpineShadersStructs", path: "spine-ios/Sources/SpineShadersStructs" - ) + ), ], cxxLanguageStandard: .cxx11 ) diff --git a/formatters/.swift-format b/formatters/.swift-format new file mode 100644 index 000000000..3670bc486 --- /dev/null +++ b/formatters/.swift-format @@ -0,0 +1,52 @@ +{ + "version": 1, + "lineLength": 150, + "indentation": { + "spaces": 4 + }, + "tabWidth": 4, + "maximumBlankLines": 2, + "respectsExistingLineBreaks": true, + "lineBreakBeforeControlFlowKeywords": false, + "lineBreakBeforeEachArgument": false, + "lineBreakBeforeEachGenericRequirement": false, + "prioritizeKeepingFunctionOutputTogether": true, + "indentConditionalCompilationBlocks": true, + "lineBreakAroundMultilineExpressionChainComponents": false, + "rules": { + "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLowerCamelCase": true, + "AmbiguousTrailingClosureOverload": true, + "BeginDocumentationCommentWithOneLineSummary": false, + "DoNotUseSemicolons": true, + "DontRepeatTypeInStaticProperties": true, + "FileScopedDeclarationPrivacy": true, + "FullyIndirectEnum": true, + "GroupNumericLiterals": true, + "IdentifiersMustBeASCII": true, + "NeverForceUnwrap": false, + "NeverUseForceTry": false, + "NeverUseImplicitlyUnwrappedOptionals": false, + "NoAccessLevelOnExtensionDeclaration": true, + "NoBlockComments": true, + "NoCasesWithOnlyFallthrough": true, + "NoEmptyTrailingClosureParentheses": true, + "NoLabelsInCasePatterns": true, + "NoLeadingUnderscores": false, + "NoParensAroundConditions": true, + "NoVoidReturnOnFunctionSignature": true, + "OneCasePerLine": true, + "OneVariableDeclarationPerLine": true, + "OnlyOneTrailingClosureArgument": false, + "OrderedImports": true, + "ReturnVoidInsteadOfEmptyTuple": true, + "UseEarlyExits": false, + "UseLetInEveryBoundCaseVariable": true, + "UseShorthandTypeNames": true, + "UseSingleLinePropertyGetter": true, + "UseSynthesizedInitializer": true, + "UseTripleSlashForDocumentationComments": true, + "UseWhereClausesInForLoops": false, + "ValidateDocumentationComments": false + } +} \ No newline at end of file diff --git a/formatters/format-cpp.sh b/formatters/format-cpp.sh index b56aa7916..c446345ea 100755 --- a/formatters/format-cpp.sh +++ b/formatters/format-cpp.sh @@ -1,16 +1,21 @@ #!/bin/bash set -e -# Format C/C++ files with clang-format -echo "Formatting C/C++ files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "C/C++ Formatting" + # Store original directory pushd "$dir" > /dev/null -if [ ! -f ".clang-format" ]; then - echo "Error: .clang-format not found in formatters directory" +log_action "Checking for formatters/.clang-format" +if [ -f ".clang-format" ]; then + log_ok +else + log_fail popd > /dev/null exit 1 fi @@ -22,47 +27,47 @@ cpp_dirs=( "../spine-cpp/src/spine" "../spine-cpp/spine-cpp-lite" "../spine-cpp/tests" - + # spine-c "../spine-c/include" "../spine-c/src" "../spine-c/src/generated" "../spine-c/tests" - + # spine-godot "../spine-godot/spine_godot" - + # spine-ue "../spine-ue/Source/SpineUE" "../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Public" "../spine-ue/Plugins/SpinePlugin/Source/SpinePlugin/Private" "../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public" "../spine-ue/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private" - + # spine-glfw "../spine-glfw/src" "../spine-glfw/example" - + # spine-sdl "../spine-sdl/src" "../spine-sdl/example" - + # spine-sfml "../spine-sfml/c/src/spine" "../spine-sfml/c/example" "../spine-sfml/cpp/src/spine" "../spine-sfml/cpp/example" - + # spine-cocos2dx "../spine-cocos2dx/spine-cocos2dx/src/spine" "../spine-cocos2dx/example/Classes" - + # spine-ios "../spine-ios/Sources/SpineCppLite" "../spine-ios/Sources/SpineCppLite/include" "../spine-ios/Sources/SpineShadersStructs" "../spine-ios/Example/Spine iOS Example" - + # spine-flutter "../spine-flutter/ios/Classes" "../spine-flutter/macos/Classes" @@ -86,22 +91,16 @@ for cpp_dir in "${cpp_dirs[@]}"; do fi done -echo "Found ${#files[@]} C/C++ files to format" -# Format all files in one call - works for both Docker and native -echo "Formatting ${#files[@]} files..." -if ! clang-format -i -style=file:".clang-format" "${files[@]}" 2>&1; then - echo "Error: clang-format failed" - errors=1 +log_action "Formatting ${#files[@]} C/C++ files" +if FORMAT_OUTPUT=$(clang-format -i -style=file:".clang-format" "${files[@]}" 2>&1); then + log_ok else - errors=0 + log_fail + log_error_output "$FORMAT_OUTPUT" + popd > /dev/null + exit 1 fi -if [ $errors -gt 0 ]; then - echo "Completed with $errors errors" -fi - -echo "C/C++ formatting complete" - # Return to original directory popd > /dev/null \ No newline at end of file diff --git a/formatters/format-csharp.sh b/formatters/format-csharp.sh index 05051686a..c82fbc76a 100755 --- a/formatters/format-csharp.sh +++ b/formatters/format-csharp.sh @@ -1,48 +1,66 @@ #!/bin/bash set -e -# Format C# files with dotnet-format -echo "Formatting C# files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "C# Formatting" + if command -v dotnet &> /dev/null; then # Store original directory pushd "$dir" > /dev/null - - # Copy .editorconfig to C# directories + cp .editorconfig ../spine-csharp/ 2>/dev/null || true cp .editorconfig ../spine-monogame/ 2>/dev/null || true cp .editorconfig ../spine-unity/ 2>/dev/null || true # Format spine-csharp + log_action "Formatting spine-csharp" pushd ../spine-csharp > /dev/null - dotnet format spine-csharp.csproj --no-restore --verbosity quiet 2>/dev/null || echo "Warning: Some issues with spine-csharp formatting" + if DOTNET_OUTPUT=$(dotnet format spine-csharp.csproj --no-restore --verbosity quiet 2>&1); then + log_ok + else + log_warn + log_detail "$DOTNET_OUTPUT" + fi popd > /dev/null - + # Format spine-monogame + log_action "Formatting spine-monogame" pushd ../spine-monogame > /dev/null - dotnet format --no-restore --verbosity quiet 2>/dev/null || echo "Warning: Some issues with spine-monogame formatting" + if DOTNET_OUTPUT=$(dotnet format --no-restore --verbosity quiet 2>&1); then + log_ok + else + log_warn + log_detail "$DOTNET_OUTPUT" + fi popd > /dev/null - + # Format spine-unity - look for .cs files directly - if [ -d ../spine-unity ]; then - echo "Formatting spine-unity C# files directly..." - pushd ../spine-unity > /dev/null - # Find all .cs files and format them using dotnet format whitespace + log_action "Formatting spine-unity C# files" + pushd ../spine-unity > /dev/null + # Find all .cs files and format them using dotnet format whitespace + cs_files=$(find . -name "*.cs" -type f -not -path "./Library/*" -not -path "./Temp/*" -not -path "./obj/*" -not -path "./bin/*" | wc -l | tr -d ' ') + if [ "$cs_files" -gt 0 ]; then find . -name "*.cs" -type f -not -path "./Library/*" -not -path "./Temp/*" -not -path "./obj/*" -not -path "./bin/*" | while read -r file; do dotnet format whitespace --include "$file" --no-restore 2>/dev/null || true done - popd > /dev/null + log_ok + else + log_skip fi + popd > /dev/null - # Clean up .editorconfig files rm -f ../spine-csharp/.editorconfig rm -f ../spine-monogame/.editorconfig rm -f ../spine-unity/.editorconfig - + # Return to original directory popd > /dev/null else - echo "Warning: dotnet not found. Skipping C# formatting." + log_fail + log_error_output "dotnet not found. Please install .NET SDK to format C# files." + exit 1 fi \ No newline at end of file diff --git a/formatters/format-dart.sh b/formatters/format-dart.sh index 558c6a95d..b54652045 100755 --- a/formatters/format-dart.sh +++ b/formatters/format-dart.sh @@ -1,22 +1,44 @@ #!/bin/bash set -e -# Format Dart files -echo "Formatting Dart files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "Dart Formatting" + # Store original directory pushd "$dir" > /dev/null if command -v dart &> /dev/null; then - find .. -name "*.dart" \ + dart_files=$(find .. -name "*.dart" \ -not -path "*/.*" \ -not -path "*/node_modules/*" \ - -not -path "*/build/*" \ - -exec dart format --page-width 120 {} + + -not -path "*/build/*" | wc -l | tr -d ' ') + + if [ "$dart_files" -gt 0 ]; then + log_action "Formatting $dart_files Dart files" + if DART_OUTPUT=$(find .. -name "*.dart" \ + -not -path "*/.*" \ + -not -path "*/node_modules/*" \ + -not -path "*/build/*" \ + -exec dart format --page-width 120 {} + 2>&1); then + log_ok + else + log_fail + log_error_output "$DART_OUTPUT" + popd > /dev/null + exit 1 + fi + else + log_action "Formatting Dart files" + log_skip + fi else - echo "Warning: dart not found. Skipping Dart formatting." + log_fail + log_error_output "dart not found. Please install Dart SDK to format Dart files." + exit 1 fi # Return to original directory diff --git a/formatters/format-haxe.sh b/formatters/format-haxe.sh index 4c02cb069..3fec62c33 100755 --- a/formatters/format-haxe.sh +++ b/formatters/format-haxe.sh @@ -1,21 +1,45 @@ #!/bin/bash set -e -# Format Haxe files -echo "Formatting Haxe files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "Haxe Formatting" + # Store original directory pushd "$dir" > /dev/null -if command -v haxelib &> /dev/null && haxelib list formatter &> /dev/null; then - # Format spine-haxe directory - if [ -d ../spine-haxe ]; then - haxelib run formatter -s ../spine-haxe +if command -v haxelib &> /dev/null; then + log_action "Checking Haxe formatter availability" + if HAXELIB_OUTPUT=$(haxelib list formatter 2>&1); then + log_ok + + # Format spine-haxe directory + if [ -d ../spine-haxe ]; then + log_action "Formatting spine-haxe directory" + if FORMATTER_OUTPUT=$(haxelib run formatter -s ../spine-haxe 2>&1); then + log_ok + else + log_fail + log_error_output "$FORMATTER_OUTPUT" + popd > /dev/null + exit 1 + fi + else + log_action "Formatting spine-haxe directory" + log_skip + fi + else + log_fail + log_error_output "Haxe formatter not found. Install with: haxelib install formatter" + exit 1 fi else - echo "Warning: haxe formatter not found. Install with: haxelib install formatter" + log_fail + log_error_output "haxelib not found. Please install Haxe to format Haxe files." + exit 1 fi # Return to original directory diff --git a/formatters/format-java.sh b/formatters/format-java.sh index fa7934c19..276645ad4 100755 --- a/formatters/format-java.sh +++ b/formatters/format-java.sh @@ -1,11 +1,13 @@ #!/bin/bash set -e -# Format Java files with Eclipse formatter -echo "Formatting Java files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "Java Formatting" + # Store original directory pushd "$dir" > /dev/null @@ -14,9 +16,17 @@ jar_file="eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies src_file="eclipse-formatter/src/main/java/com/esotericsoftware/spine/formatter/EclipseFormatter.java" if [ ! -f "$jar_file" ] || [ "$src_file" -nt "$jar_file" ]; then - echo "Building Eclipse formatter..." + log_action "Building Eclipse formatter" pushd eclipse-formatter > /dev/null - mvn -q clean package + if MVN_OUTPUT=$(mvn -q clean package 2>&1); then + log_ok + else + log_fail + log_error_output "$MVN_OUTPUT" + popd > /dev/null + popd > /dev/null + exit 1 + fi popd > /dev/null fi @@ -30,13 +40,22 @@ java_files=$(find ../spine-libgdx ../spine-android -name "*.java" -type f \ # Run the formatter if [ -n "$java_files" ]; then - echo "Running Eclipse formatter on Java files..." - java -jar eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies.jar \ + java_count=$(echo "$java_files" | wc -l | tr -d ' ') + log_action "Formatting $java_count Java files" + if FORMATTER_OUTPUT=$(java -jar eclipse-formatter/target/eclipse-formatter-1.0.0-jar-with-dependencies.jar \ eclipse-formatter.xml \ - $java_files + $java_files 2>&1); then + log_ok + else + log_fail + log_error_output "$FORMATTER_OUTPUT" + popd > /dev/null + exit 1 + fi +else + log_action "Formatting Java files" + log_skip fi -echo "Java formatting complete" - # Return to original directory popd > /dev/null \ No newline at end of file diff --git a/formatters/format-swift.sh b/formatters/format-swift.sh index 819931c55..4a722e2cd 100755 --- a/formatters/format-swift.sh +++ b/formatters/format-swift.sh @@ -1,22 +1,45 @@ #!/bin/bash set -e -# Format Swift files -echo "Formatting Swift files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "Swift Formatting" + # Store original directory pushd "$dir" > /dev/null if command -v swift-format &> /dev/null; then - find .. -name "*.swift" \ + + swift_files=$(find .. -name "*.swift" -type f \ -not -path "*/.*" \ -not -path "*/build/*" \ - -not -path "*/DerivedData/*" \ - | xargs swift-format -i + -not -path "*/DerivedData/*" | wc -l | tr -d ' ') + + if [ "$swift_files" -gt 0 ]; then + log_action "Formatting $swift_files Swift files" + if SWIFT_OUTPUT=$(find .. -name "*.swift" -type f \ + -not -path "*/.*" \ + -not -path "*/build/*" \ + -not -path "*/DerivedData/*" \ + -print0 | xargs -0 swift-format --in-place --configuration .swift-format 2>&1); then + log_ok + else + log_fail + log_error_output "$SWIFT_OUTPUT" + popd > /dev/null + exit 1 + fi + else + log_action "Formatting Swift files" + log_skip + fi else - echo "Warning: swift-format not found. Install from https://github.com/apple/swift-format" + log_fail + log_error_output "swift-format not found. Install via brew install swift-format" + exit 1 fi # Return to original directory diff --git a/formatters/format-ts.sh b/formatters/format-ts.sh index 2ea327518..67776ac29 100755 --- a/formatters/format-ts.sh +++ b/formatters/format-ts.sh @@ -1,29 +1,53 @@ #!/bin/bash set -e -# Format TypeScript files with tsfmt -echo "Formatting TypeScript files..." - dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +# Source logging utilities +source "$dir/logging/logging.sh" + +log_title "TypeScript Formatting" + # Store original directory pushd "$dir" > /dev/null # Check if tsfmt.json files match -if ! cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json; then - echo -e "\033[1;31mERROR: spine-ts/tsfmt.json and tests/tsfmt.json differ!\033[0m" - echo -e "\033[1;31mPlease sync them to ensure consistent formatting.\033[0m" +log_action "Checking tsfmt.json consistency" +if CMP_OUTPUT=$(cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json 2>&1); then + log_ok +else + log_fail + log_error_output "spine-ts/tsfmt.json and tests/tsfmt.json differ!" + log_detail "Please sync them to ensure consistent formatting." popd > /dev/null exit 1 fi # Format TypeScript files +log_action "Formatting spine-ts TypeScript files" pushd ../spine-ts > /dev/null -npm run format +if NPM_OUTPUT=$(npm run format 2>&1); then + log_ok +else + log_fail + log_error_output "$NPM_OUTPUT" + popd > /dev/null + popd > /dev/null + exit 1 +fi popd > /dev/null +log_action "Formatting tests TypeScript files" pushd ../tests > /dev/null -npm run format +if NPM_OUTPUT=$(npm run format 2>&1); then + log_ok +else + log_fail + log_error_output "$NPM_OUTPUT" + popd > /dev/null + popd > /dev/null + exit 1 +fi popd > /dev/null # Return to original directory diff --git a/formatters/format.sh b/formatters/format.sh index 3e47fdf40..3ecfa40ac 100755 --- a/formatters/format.sh +++ b/formatters/format.sh @@ -89,7 +89,8 @@ while [[ $# -gt 0 ]]; do shift ;; *) - log_fail "Unknown option: $1" + log_fail + log_error_output "Unknown option: $1" log_detail "Use --help for usage information" exit 1 ;; @@ -98,54 +99,33 @@ done log_title "Code Formatting" -# Call individual formatter scripts +# Call individual formatter scripts (they handle their own logging) if [ "$FORMAT_CPP" = true ]; then - log_section "C/C++" - log_action "Formatting C/C++ files" "$dir/format-cpp.sh" - log_ok "C/C++ formatting completed" fi if [ "$FORMAT_JAVA" = true ]; then - log_section "Java" - log_action "Formatting Java files" "$dir/format-java.sh" - log_ok "Java formatting completed" fi if [ "$FORMAT_CSHARP" = true ]; then - log_section "C#" - log_action "Formatting C# files" "$dir/format-csharp.sh" - log_ok "C# formatting completed" fi if [ "$FORMAT_TS" = true ]; then - log_section "TypeScript" - log_action "Formatting TypeScript files" "$dir/format-ts.sh" - log_ok "TypeScript formatting completed" fi if [ "$FORMAT_DART" = true ]; then - log_section "Dart" - log_action "Formatting Dart files" "$dir/format-dart.sh" - log_ok "Dart formatting completed" fi if [ "$FORMAT_HAXE" = true ]; then - log_section "Haxe" - log_action "Formatting Haxe files" "$dir/format-haxe.sh" - log_ok "Haxe formatting completed" fi if [ "$FORMAT_SWIFT" = true ]; then - log_section "Swift" - log_action "Formatting Swift files" "$dir/format-swift.sh" - log_ok "Swift formatting completed" fi log_summary "✓ All formatting completed" \ No newline at end of file diff --git a/formatters/logging/logging.sh b/formatters/logging/logging.sh index 33d95a462..a710637a1 100644 --- a/formatters/logging/logging.sh +++ b/formatters/logging/logging.sh @@ -43,42 +43,41 @@ NC='\033[0m' # No Color # Design principles: # 1. Minimal visual noise - use color sparingly for emphasis -# 2. Clear hierarchy - different levels of information have different treatments +# 2. Clear hierarchy - different levels of information have different treatments # 3. Consistent spacing - clean vertical rhythm # 4. Accessible - readable without colors # Main header for script/tool name log_title() { - echo "" - echo -e "${BOLD}$1${NC}" - echo "" + echo -e "${GREEN}${BOLD}$1${NC}" } -# Section headers for major phases -log_section() { - echo -e "${BOLD}${BLUE}$1${NC}" -} -# Individual actions/steps +# Individual actions/steps - inline result format log_action() { - echo -e " $1..." + echo -n " $1... " } -# Results - success/failure/info +# Results - success/failure/info (on same line) log_ok() { - echo -e " ${GREEN}✓${NC} $1" + echo -e "${GREEN}✓${NC}" } log_fail() { - echo -e " ${RED}✗${NC} $1" + echo -e "${RED}✗${NC}" } log_warn() { - echo -e " ${YELLOW}!${NC} $1" + echo -e "${YELLOW}!${NC}" } log_skip() { - echo -e " ${GRAY}-${NC} $1" + echo -e "${GRAY}-${NC}" +} + +# For errors, show full output prominently (not grayed) +log_error_output() { + echo "$1" } # Final summary diff --git a/formatters/logging/terminal-logging-guide.md b/formatters/logging/terminal-logging-guide.md index 019fb273c..5f5f83081 100644 --- a/formatters/logging/terminal-logging-guide.md +++ b/formatters/logging/terminal-logging-guide.md @@ -18,48 +18,51 @@ This guide defines the terminal output style for all bash scripts in the Spine R - **Usage**: Once at the beginning of script execution ```bash -log_title "Spine-C++ Test" +log_title "Spine-C++ Build" ``` -### 2. Section (`log_section`) -- **Purpose**: Major phases or groups of operations -- **Style**: Bold blue text, no extra spacing -- **Usage**: Build, Test, Deploy, etc. - -```bash -log_section "Build" -log_section "Test" -``` - -### 3. Action (`log_action`) -- **Purpose**: Individual operations in progress -- **Style**: Indented, followed by "..." -- **Usage**: Before starting an operation +### 2. Action + Result (inline format) +- **Purpose**: Individual operations with immediate result +- **Style**: Action on same line as result for density +- **Usage**: `log_action` followed immediately by `log_ok/fail/warn/skip` ```bash log_action "Building all variants" -log_action "Testing headless-test" +log_ok # Green ✓ on same line + +log_action "Testing headless-test" +log_fail # Red ✗ on same line ``` -### 4. Results -- **Purpose**: Outcome of operations -- **Style**: Indented with colored symbols +**Results**: +- `log_ok` - Green ✓ (success) +- `log_fail` - Red ✗ (failure) +- `log_warn` - Yellow ! (warning) +- `log_skip` - Gray - (skipped) + +### 4. Error Output (`log_error_output`) +- **Purpose**: Full error output when operations fail +- **Style**: Normal text (not grayed), prominent +- **Usage**: Show command output after failures ```bash -log_ok "Build completed" # Green ✓ -log_fail "Build failed" # Red ✗ -log_warn "Deprecated feature" # Yellow ! -log_skip "Not supported on macOS" # Gray - +log_action "Building" +if BUILD_OUTPUT=$(command 2>&1); then + log_ok +else + log_fail + log_error_output "$BUILD_OUTPUT" +fi ``` ### 5. Detail (`log_detail`) -- **Purpose**: Secondary information, error output, debug info +- **Purpose**: Secondary information, debug info (not errors) - **Style**: Gray text, indented -- **Usage**: Additional context, error messages +- **Usage**: Additional context, platform info ```bash log_detail "Platform: Darwin" -log_detail "$ERROR_OUTPUT" +log_detail "Branch: main" ``` ### 6. Summary (`log_summary`) @@ -78,58 +81,90 @@ log_summary "✗ Tests failed (3/5)" #!/bin/bash source ../formatters/logging/logging.sh -log_title "Spine-C++ Test" +log_title "Spine-C++ Build" log_detail "Platform: $(uname)" -log_section "Build" -log_action "Building all variants" -if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then - log_ok "Build completed" +log_action "Configuring debug build" +if CMAKE_OUTPUT=$(cmake --preset=debug . 2>&1); then + log_ok else - log_fail "Build failed" - log_detail "$BUILD_OUTPUT" + log_fail + log_error_output "$CMAKE_OUTPUT" exit 1 fi -log_section "Test" -log_action "Testing headless-test" -if test_result; then - log_ok "headless-test" +log_action "Building" +if BUILD_OUTPUT=$(cmake --build --preset=debug 2>&1); then + log_ok else - log_fail "headless-test - execution failed" - log_detail "$error_output" + log_fail + log_error_output "$BUILD_OUTPUT" + exit 1 fi -log_summary "✓ All tests passed (2/2)" +log_summary "✓ Build successful" ``` ## Output Preview +**Success case:** ``` -Spine-C++ Test - +Spine-C++ Build Platform: Darwin -Build - Building all variants... - ✓ Build completed + Configuring debug build... ✓ + Building... ✓ -Test - Testing headless-test... - ✓ headless-test - Testing headless-test-nostdcpp... - ✓ headless-test-nostdcpp +✓ Build successful +``` -✓ All tests passed (2/2) +**Failure case:** +``` +Spine-C++ Build +Platform: Darwin + + Configuring debug build... ✗ +CMake Error: Could not find cmake file... +(full error output shown prominently) ``` ## Error Handling Best Practices 1. **Capture output**: Use `OUTPUT=$(command 2>&1)` to capture both stdout and stderr -2. **Check exit codes**: Always check if critical operations succeeded -3. **Show details on failure**: Use `log_detail` to show error output +2. **Check exit codes**: Always check if critical operations succeeded +3. **Show errors prominently**: Use `log_error_output` for command failures (not grayed) 4. **Fail fast**: Exit immediately on critical failures -5. **Clear error messages**: Make failure reasons obvious +5. **Use inline results**: `log_action` + `log_ok/fail` for dense, scannable output + +## Calling Other Scripts + +When calling other bash scripts that have their own logging: + +1. **Trust their logging**: Don't wrap calls in redundant log actions +2. **Capture output for errors**: Use `OUTPUT=$(script.sh 2>&1)` to capture output and only show on failure +3. **Let them handle success**: Avoid "script completed" messages when the script shows its own status + +**Good**: +```bash +# Let the script handle its own logging +../formatters/format.sh cpp + +# Or capture output and only show on error +if output=$(./setup.sh 2>&1); then + log_ok "Setup completed" +else + log_fail "Setup failed" + log_detail "$output" +fi +``` + +**Avoid**: +```bash +# This creates duplicate logging +log_action "Formatting C++ files" +../formatters/format.sh cpp +log_ok "C++ formatting completed" +``` ```bash if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then diff --git a/spine-c/build.sh b/spine-c/build.sh index 44fe48cf1..5d56f9c90 100755 --- a/spine-c/build.sh +++ b/spine-c/build.sh @@ -34,34 +34,30 @@ fi # Run codegen if requested if [ "$1" = "codegen" ]; then - log_title "Spine-C Code Generation" - - log_section "Generate" + log_title "spine-c code generation" + log_action "Generating C bindings" if CODEGEN_OUTPUT=$(npx -y tsx codegen/src/index.ts 2>&1); then - log_ok "Code generation completed" + log_ok else - log_fail "Code generation failed" - log_detail "$CODEGEN_OUTPUT" + log_fail + log_error_output "$CODEGEN_OUTPUT" exit 1 fi - - log_section "Format" - log_action "Formatting generated C++ files" + ../formatters/format.sh cpp - + log_summary "✓ Code generation successful" exit 0 fi -log_title "Spine-C Build" +log_title "spine-c build" # Clean only if explicitly requested if [ "$1" = "clean" ]; then - log_section "Clean" - log_action "Removing build directory" + log_action "Cleaning build directory" rm -rf build - log_ok "Cleaned" + log_ok fi # Determine build type @@ -71,23 +67,21 @@ if [ "$1" = "release" ]; then fi # Configure and build -log_section "Configure" log_action "Configuring $BUILD_TYPE build" if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE . 2>&1); then - log_ok "Configured" + log_ok else - log_fail "Configuration failed" - log_detail "$CMAKE_OUTPUT" + log_fail + log_error_output "$CMAKE_OUTPUT" exit 1 fi -log_section "Build" log_action "Building" if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then - log_ok "Build completed" + log_ok else - log_fail "Build failed" - log_detail "$BUILD_OUTPUT" + log_fail + log_error_output "$BUILD_OUTPUT" exit 1 fi diff --git a/spine-cpp/build.sh b/spine-cpp/build.sh index 1533cfd41..53f7b352d 100755 --- a/spine-cpp/build.sh +++ b/spine-cpp/build.sh @@ -33,34 +33,31 @@ for arg in "$@"; do esac done -log_title "Spine-C++ Build" +log_title "spine-cpp build" # Clean if requested if [ "$CLEAN" = "true" ]; then - log_section "Clean" - log_action "Removing build directory" + log_action "Cleaning build directory" rm -rf build - log_ok "Cleaned" + log_ok fi # Configure and build -log_section "Configure" log_action "Configuring $BUILD_TYPE build" if CMAKE_OUTPUT=$(cmake --preset=$BUILD_TYPE $NOFILEIO . 2>&1); then - log_ok "Configured" + log_ok else - log_fail "Configuration failed" - log_detail "$CMAKE_OUTPUT" + log_fail + log_error_output "$CMAKE_OUTPUT" exit 1 fi -log_section "Build" log_action "Building" if BUILD_OUTPUT=$(cmake --build --preset=$BUILD_TYPE 2>&1); then - log_ok "Build completed" + log_ok else - log_fail "Build failed" - log_detail "$BUILD_OUTPUT" + log_fail + log_error_output "$BUILD_OUTPUT" exit 1 fi diff --git a/spine-cpp/tests/test.sh b/spine-cpp/tests/test.sh index 9b14d44aa..eee469f98 100755 --- a/spine-cpp/tests/test.sh +++ b/spine-cpp/tests/test.sh @@ -3,7 +3,7 @@ # # Tests all spine-cpp build variants with spineboy example data: # - headless-test (regular dynamic) -# - headless-test-nostdcpp (nostdcpp dynamic) +# - headless-test-nostdcpp (nostdcpp dynamic) # - headless-test-static (regular static, Linux only) # - headless-test-nostdcpp-static (nostdcpp static, Linux only) @@ -35,7 +35,6 @@ EXPECTED_OUTPUT="=== SKELETON DATA === log_title "Spine-C++ Test" log_detail "Platform: $(uname)" -log_section "Build" log_action "Building all variants" if BUILD_OUTPUT=$(./build.sh clean release 2>&1); then log_ok "Build completed" @@ -45,8 +44,6 @@ else exit 1 fi -log_section "Test" - test_count=0 pass_count=0 @@ -54,12 +51,12 @@ for exe in build/headless-test*; do if [ -f "$exe" ] && [ -x "$exe" ]; then exe_name=$(basename "$exe") log_action "Testing $exe_name" - + test_count=$((test_count + 1)) - + if OUTPUT=$("$exe" $SPINEBOY_SKEL $SPINEBOY_ATLAS $SPINEBOY_ANIM 2>&1); then actual_output=$(echo "$OUTPUT" | head -10) - + if [ "$actual_output" = "$EXPECTED_OUTPUT" ]; then log_ok "$exe_name" pass_count=$((pass_count + 1)) diff --git a/spine-flutter/example/ios/Runner/AppDelegate.swift b/spine-flutter/example/ios/Runner/AppDelegate.swift index 70693e4a8..175629709 100644 --- a/spine-flutter/example/ios/Runner/AppDelegate.swift +++ b/spine-flutter/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,13 @@ -import UIKit import Flutter +import UIKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } } diff --git a/spine-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift b/spine-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a5..bfa0333a3 100644 --- a/spine-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/spine-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,5 @@ import FlutterMacOS import Foundation - func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { } diff --git a/spine-flutter/example/macos/Runner/AppDelegate.swift b/spine-flutter/example/macos/Runner/AppDelegate.swift index d53ef6437..bfe34620c 100644 --- a/spine-flutter/example/macos/Runner/AppDelegate.swift +++ b/spine-flutter/example/macos/Runner/AppDelegate.swift @@ -3,7 +3,7 @@ import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } } diff --git a/spine-flutter/example/macos/Runner/MainFlutterWindow.swift b/spine-flutter/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..f30efc176 100644 --- a/spine-flutter/example/macos/Runner/MainFlutterWindow.swift +++ b/spine-flutter/example/macos/Runner/MainFlutterWindow.swift @@ -2,14 +2,14 @@ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) - RegisterGeneratedPlugins(registry: flutterViewController) + RegisterGeneratedPlugins(registry: flutterViewController) - super.awakeFromNib() - } + super.awakeFromNib() + } } diff --git a/spine-haxe/build.sh b/spine-haxe/build.sh index c77444023..7b04d3a6f 100755 --- a/spine-haxe/build.sh +++ b/spine-haxe/build.sh @@ -26,18 +26,24 @@ if echo "$COMMIT_MSG" | grep -qE '^\[haxe\] Release [0-9]+\.[0-9]+\.[0-9]+$'; th if [ ! -z "$HAXE_UPDATE_URL" ] && [ ! -z "$BRANCH" ]; then log_section "Deploy" log_action "Creating release package" - zip -r "spine-haxe-$VERSION.zip" \ + if ZIP_OUTPUT=$(zip -r "spine-haxe-$VERSION.zip" \ haxelib.json \ LICENSE \ README.md \ - spine-haxe > /dev/null 2>&1 - log_ok "Package created" + spine-haxe 2>&1); then + log_ok + else + log_fail + log_error_output "$ZIP_OUTPUT" + exit 1 + fi log_action "Uploading to $HAXE_UPDATE_URL$BRANCH" - if curl -f -F "file=@spine-haxe-$VERSION.zip" "$HAXE_UPDATE_URL$BRANCH" > /dev/null 2>&1; then - log_ok "Package deployed" + if CURL_OUTPUT=$(curl -f -F "file=@spine-haxe-$VERSION.zip" "$HAXE_UPDATE_URL$BRANCH" 2>&1); then + log_ok else - log_fail "Upload failed" + log_fail + log_error_output "$CURL_OUTPUT" exit 1 fi diff --git a/spine-ios/Example - Cocoapods/Spine iOS Example/SimpleAnimation.swift b/spine-ios/Example - Cocoapods/Spine iOS Example/SimpleAnimation.swift index 9bb24a1cd..a434e65b4 100644 --- a/spine-ios/Example - Cocoapods/Spine iOS Example/SimpleAnimation.swift +++ b/spine-ios/Example - Cocoapods/Spine iOS Example/SimpleAnimation.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct SimpleAnimation: View { @@ -46,10 +46,10 @@ struct SimpleAnimation: View { var body: some View { SpineView( from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"), -// from: .http( -// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!, -// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")! -// ), + // from: .http( + // atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!, + // skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")! + // ), controller: controller, mode: .fit, alignment: .center diff --git a/spine-ios/Example - Cocoapods/Spine iOS Example/Spine_iOS_ExampleApp.swift b/spine-ios/Example - Cocoapods/Spine iOS Example/Spine_iOS_ExampleApp.swift index 06ca47ab5..e08879225 100644 --- a/spine-ios/Example - Cocoapods/Spine iOS Example/Spine_iOS_ExampleApp.swift +++ b/spine-ios/Example - Cocoapods/Spine iOS Example/Spine_iOS_ExampleApp.swift @@ -5,8 +5,8 @@ // Created by Denis Andrašec on 29.05.24. // -import SwiftUI import SpineCppLite +import SwiftUI @main struct Spine_iOS_ExampleApp: App { diff --git a/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift b/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift index c9d9e8962..c8720f30b 100644 --- a/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift +++ b/spine-ios/Example/Spine iOS Example/AnimationStateEvents.swift @@ -27,12 +27,12 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine import SpineCppLite +import SwiftUI struct AnimationStateEvents: View { - + @StateObject var controller = SpineController( onInitialized: { controller in @@ -42,23 +42,25 @@ struct AnimationStateEvents: View { controller.animationStateData.defaultMix = 0.2 let walk = controller.animationState.setAnimationByName(trackIndex: 0, animationName: "walk", loop: true) controller.animationStateWrapper.setTrackEntryListener(entry: walk) { type, entry, event in - print("Walk animation event \(type)"); + print("Walk animation event \(type)") } controller.animationState.addAnimationByName(trackIndex: 0, animationName: "jump", loop: false, delay: 2) let run = controller.animationState.addAnimationByName(trackIndex: 0, animationName: "run", loop: true, delay: 0) controller.animationStateWrapper.setTrackEntryListener(entry: run) { type, entry, event in - print("Run animation event \(type)"); + print("Run animation event \(type)") } controller.animationStateWrapper.setStateListener { type, entry, event in if type == SPINE_EVENT_TYPE_EVENT, let event { - print("User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }") + print( + "User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }" + ) } } let current = controller.animationState.getCurrent(trackIndex: 0)?.animation.name ?? "--" print("Current: \(current)") } ) - + var body: some View { VStack { Text("See output in console!") diff --git a/spine-ios/Example/Spine iOS Example/DebugRendering.swift b/spine-ios/Example/Spine iOS Example/DebugRendering.swift index ede891996..f9cefd6de 100644 --- a/spine-ios/Example/Spine iOS Example/DebugRendering.swift +++ b/spine-ios/Example/Spine iOS Example/DebugRendering.swift @@ -27,14 +27,14 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct DebugRendering: View { - + @StateObject var model = DebugRenderingModel() - + var body: some View { ZStack { Color.red.ignoresSafeArea() @@ -61,13 +61,13 @@ struct DebugRendering: View { } final class DebugRenderingModel: ObservableObject { - + @Published var controller: SpineController! - + @Published var boneRects = [BoneRect]() - + init() { controller = SpineController( onInitialized: { controller in @@ -77,8 +77,9 @@ final class DebugRenderingModel: ObservableObject { loop: true ) }, - onAfterPaint: { - [weak self] controller in guard let self else { return } + onAfterPaint: { + [weak self] controller in + guard let self else { return } boneRects = controller.drawable.skeleton.bones.map { bone in let position = controller.fromSkeletonCoordinates( position: CGPointMake(CGFloat(bone.worldX), CGFloat(bone.worldY)) diff --git a/spine-ios/Example/Spine iOS Example/DisableRendering.swift b/spine-ios/Example/Spine iOS Example/DisableRendering.swift index 18393b710..63c20b5c2 100644 --- a/spine-ios/Example/Spine iOS Example/DisableRendering.swift +++ b/spine-ios/Example/Spine iOS Example/DisableRendering.swift @@ -27,11 +27,11 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct DisableRendering: View { - + @StateObject var controller = SpineController( onInitialized: { controller in @@ -42,10 +42,10 @@ struct DisableRendering: View { ) } ) - + @State var isRendering: Bool? - + var body: some View { VStack { List { @@ -54,7 +54,7 @@ struct DisableRendering: View { Text("Rendering is disabled when the spine view moves out of the viewport, preserving CPU/GPU resources.") .foregroundColor(.secondary) } - + SpineView( from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"), controller: controller, @@ -69,13 +69,13 @@ struct DisableRendering: View { isRendering = false print("rendering disabled") } - + Text("Foo") .frame(minHeight: 400) - + Text("Bar") .frame(minHeight: 400) - + Text("Baz") .frame(minHeight: 400) } diff --git a/spine-ios/Example/Spine iOS Example/DressUp.swift b/spine-ios/Example/Spine iOS Example/DressUp.swift index 5d48d3f53..7fe0f66a2 100644 --- a/spine-ios/Example/Spine iOS Example/DressUp.swift +++ b/spine-ios/Example/Spine iOS Example/DressUp.swift @@ -27,15 +27,15 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine import SpineCppLite +import SwiftUI struct DressUp: View { - + @StateObject var model = DressUpModel() - + var body: some View { HStack(spacing: 0) { List { @@ -51,9 +51,9 @@ struct DressUp: View { } } .listStyle(.plain) - + Divider() - + if let drawable = model.drawable { SpineView( from: .drawable(drawable), @@ -74,23 +74,23 @@ struct DressUp: View { } final class DressUpModel: ObservableObject { - + let thumbnailSize = CGSize(width: 200, height: 200) - + @Published var controller: SpineController - + @Published var drawable: SkeletonDrawableWrapper? - + @Published var skinImages = [String: CGImage]() - + @Published var selectedSkins = [String: Bool]() - + private var customSkin: Skin? - + init() { controller = SpineController( onInitialized: { controller in @@ -129,28 +129,28 @@ final class DressUpModel: ObservableObject { } } } - + deinit { drawable?.dispose() customSkin?.dispose() } - + func toggleSkin(skinName: String) { if let drawable { toggleSkin(skinName: skinName, drawable: drawable) } } - + func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) { selectedSkins[skinName] = !(selectedSkins[skinName] ?? false) customSkin?.dispose() customSkin = Skin.create(name: "custom-skin") for skinName in selectedSkins.keys { - if selectedSkins[skinName] == true { - if let skin = drawable.skeletonData.findSkin(name: skinName) { - customSkin?.addSkin(other: skin) - } - } + if selectedSkins[skinName] == true { + if let skin = drawable.skeletonData.findSkin(name: skinName) { + customSkin?.addSkin(other: skin) + } + } } drawable.skeleton.skin = customSkin drawable.skeleton.setToSetupPose() diff --git a/spine-ios/Example/Spine iOS Example/IKFollowing.swift b/spine-ios/Example/Spine iOS Example/IKFollowing.swift index 71a42c2c1..1ff23f7f2 100644 --- a/spine-ios/Example/Spine iOS Example/IKFollowing.swift +++ b/spine-ios/Example/Spine iOS Example/IKFollowing.swift @@ -27,14 +27,14 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct IKFollowing: View { - + @StateObject var model = IKFollowingModel() - + var body: some View { SpineView( from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"), @@ -64,13 +64,13 @@ struct IKFollowing: View { } final class IKFollowingModel: ObservableObject { - + @Published var controller: SpineController! - + @Published var crossHairPosition: CGPoint? - + init() { controller = SpineController( onInitialized: { controller in @@ -85,14 +85,15 @@ final class IKFollowingModel: ObservableObject { loop: true ) }, - onAfterUpdateWorldTransforms: { - [weak self] controller in guard let self else { return } + onAfterUpdateWorldTransforms: { + [weak self] controller in + guard let self else { return } guard let worldPosition = self.crossHairPosition else { return } let bone = controller.skeleton.findBone(boneName: "crosshair")! if let parent = bone.parent { - let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y)) + let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y)) bone.x = position.x bone.y = position.y } diff --git a/spine-ios/Example/Spine iOS Example/MainView.swift b/spine-ios/Example/Spine iOS Example/MainView.swift index 5136e999b..9575dc220 100644 --- a/spine-ios/Example/Spine iOS Example/MainView.swift +++ b/spine-ios/Example/Spine iOS Example/MainView.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct MainView: View { var body: some View { diff --git a/spine-ios/Example/Spine iOS Example/Physics.swift b/spine-ios/Example/Spine iOS Example/Physics.swift index 90e384a56..018982dfc 100644 --- a/spine-ios/Example/Spine iOS Example/Physics.swift +++ b/spine-ios/Example/Spine iOS Example/Physics.swift @@ -27,28 +27,28 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct Physics: View { - + @StateObject var model = PhysicsModel() - + var body: some View { - ZStack { - Color(red: 51 / 255, green: 51 / 255, blue: 51 / 255).ignoresSafeArea() - SpineView( - from: .bundle(atlasFileName: "celestial-circus-pma.atlas", skeletonFileName: "celestial-circus-pro.skel"), - controller: model.controller - ) - .gesture( - DragGesture(minimumDistance: 0) - .onChanged { gesture in - model.updateBonePosition(position: gesture.location) - } - ) - } + ZStack { + Color(red: 51 / 255, green: 51 / 255, blue: 51 / 255).ignoresSafeArea() + SpineView( + from: .bundle(atlasFileName: "celestial-circus-pma.atlas", skeletonFileName: "celestial-circus-pro.skel"), + controller: model.controller + ) + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { gesture in + model.updateBonePosition(position: gesture.location) + } + ) + } .navigationTitle("Physics (drag anywhere)") .navigationBarTitleDisplayMode(.inline) } @@ -59,16 +59,16 @@ struct Physics: View { } final class PhysicsModel: ObservableObject { - + @Published var controller: SpineController! - + @Published var mousePosition: CGPoint? - + @Published var lastMousePosition: CGPoint? - + init() { controller = SpineController( onInitialized: { controller in @@ -84,8 +84,9 @@ final class PhysicsModel: ObservableObject { ) }, onAfterUpdateWorldTransforms: { - [weak self] controller in guard let self else { return } - + [weak self] controller in + guard let self else { return } + guard let lastMousePosition else { self.lastMousePosition = mousePosition return @@ -102,7 +103,7 @@ final class PhysicsModel: ObservableObject { } ) } - + func updateBonePosition(position: CGPoint) { mousePosition = controller.toSkeletonCoordinates(position: position) } diff --git a/spine-ios/Example/Spine iOS Example/PlayPauseAnimation.swift b/spine-ios/Example/Spine iOS Example/PlayPauseAnimation.swift index 74bd4f7e3..af04b7a35 100644 --- a/spine-ios/Example/Spine iOS Example/PlayPauseAnimation.swift +++ b/spine-ios/Example/Spine iOS Example/PlayPauseAnimation.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct PlayPauseAnimation: View { @@ -46,10 +46,10 @@ struct PlayPauseAnimation: View { var body: some View { SpineView( from: .bundle(atlasFileName: "dragon.atlas", skeletonFileName: "dragon-ess.skel"), -// from: .http( -// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon.atlas")!, -// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon-ess.skel")! -// ), + // from: .http( + // atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon.atlas")!, + // skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/dragon/dragon-ess.skel")! + // ), controller: controller, boundsProvider: SkinAndAnimationBounds(animation: "flying") ) diff --git a/spine-ios/Example/Spine iOS Example/SimpleAnimation.swift b/spine-ios/Example/Spine iOS Example/SimpleAnimation.swift index 9bb24a1cd..a434e65b4 100644 --- a/spine-ios/Example/Spine iOS Example/SimpleAnimation.swift +++ b/spine-ios/Example/Spine iOS Example/SimpleAnimation.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI struct SimpleAnimation: View { @@ -46,10 +46,10 @@ struct SimpleAnimation: View { var body: some View { SpineView( from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"), -// from: .http( -// atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!, -// skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")! -// ), + // from: .http( + // atlasURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy.atlas")!, + // skeletonURL: URL(string: "https://github.com/esotericsoftware/spine-runtimes/raw/spine-ios/spine-ios/Example/Spine%20iOS%20Example/Assets/spineboy/spineboy-pro.skel")! + // ), controller: controller, mode: .fit, alignment: .center diff --git a/spine-ios/Example/Spine iOS Example/SimpleAnimationViewControllerRepresentable.swift b/spine-ios/Example/Spine iOS Example/SimpleAnimationViewControllerRepresentable.swift index caf9d33e5..4694a4f0f 100644 --- a/spine-ios/Example/Spine iOS Example/SimpleAnimationViewControllerRepresentable.swift +++ b/spine-ios/Example/Spine iOS Example/SimpleAnimationViewControllerRepresentable.swift @@ -31,11 +31,11 @@ import SwiftUI struct SimpleAnimationViewControllerRepresentable: UIViewControllerRepresentable { typealias UIViewControllerType = SimpleAnimationViewController - + func makeUIViewController(context: Context) -> SimpleAnimationViewController { return SimpleAnimationViewController() } - + func updateUIViewController(_ uiViewController: SimpleAnimationViewController, context: Context) { // } diff --git a/spine-ios/Example/Spine iOS Example/SpineExampleApp.swift b/spine-ios/Example/Spine iOS Example/SpineExampleApp.swift index 9e628f8a4..d3370e253 100644 --- a/spine-ios/Example/Spine iOS Example/SpineExampleApp.swift +++ b/spine-ios/Example/Spine iOS Example/SpineExampleApp.swift @@ -27,12 +27,12 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SwiftUI import Spine +import SwiftUI @main struct SpineExampleApp: App { - + var body: some Scene { WindowGroup { NavigationView { diff --git a/spine-ios/Sources/Spine/AnimationStateWrapper.swift b/spine-ios/Sources/Spine/AnimationStateWrapper.swift index 4bc1ef415..958cce528 100644 --- a/spine-ios/Sources/Spine/AnimationStateWrapper.swift +++ b/spine-ios/Sources/Spine/AnimationStateWrapper.swift @@ -39,20 +39,20 @@ public typealias AnimationStateListener = (_ type: EventType, _ entry: TrackEntr @objc(SpineAnimationStateWrapper) @objcMembers public final class AnimationStateWrapper: NSObject { - + public let animationState: AnimationState public let aninationStateEvents: AnimationStateEvents - + private var trackEntryListeners = [spine_track_entry: AnimationStateListener]() - + private var stateListener: AnimationStateListener? - + public init(animationState: AnimationState, aninationStateEvents: AnimationStateEvents) { self.animationState = animationState self.aninationStateEvents = aninationStateEvents super.init() } - + /// The listener for events generated by the provided ``TrackEntry``, or nil. /// /// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation @@ -64,18 +64,18 @@ public final class AnimationStateWrapper: NSObject { trackEntryListeners.removeValue(forKey: entry.wrappee) } } - + /// Increments each track entry ``TrackEntry/trackTime``, setting queued animations as current if needed. public func update(delta: Float) { animationState.update(delta: delta) - + let numEvents = spine_animation_state_events_get_num_events(aninationStateEvents.wrappee) for i in 0.. CGRect { let data = drawable.skeletonData let oldSkin: Skin? = drawable.skeleton.skin @@ -110,7 +110,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { } } drawable.skeleton.skin = customSkin - drawable.skeleton.setToSetupPose(); + drawable.skeleton.setToSetupPose() let animation = animation.flatMap { data.findAnimation(name: $0) } var minX = Float.Magnitude.greatestFiniteMagnitude @@ -122,14 +122,14 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { let steps = Int(max(Double(animation.duration) / stepTime, 1.0)) for i in 0.. 0 ? Float(stepTime) : 0.0) - let bounds = drawable.skeleton.bounds; + let bounds = drawable.skeleton.bounds minX = min(minX, bounds.x) minY = min(minY, bounds.y) maxX = max(maxX, minX + bounds.width) maxY = max(maxY, minY + bounds.height) } } else { - let bounds = drawable.skeleton.bounds; + let bounds = drawable.skeleton.bounds minX = bounds.x minY = bounds.y maxX = minX + bounds.width @@ -137,7 +137,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { } drawable.skeleton.setSkinByName(skinName: "default") drawable.animationState.clearTracks() - + if let oldSkin { drawable.skeleton.skin = oldSkin } @@ -145,14 +145,15 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider { drawable.update(delta: 0) customSkin.dispose() return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY)) - } + } } /// How a view should be inscribed into another view. @objc public enum ContentMode: Int { - case fit /// As large as possible while still containing the source view entirely within the target view. - case fill /// Fill the target view by distorting the source's aspect ratio. + case fit + /// As large as possible while still containing the source view entirely within the target view. + case fill/// Fill the target view by distorting the source's aspect ratio. } /// How a view should aligned withing another view. @@ -167,7 +168,7 @@ public enum Alignment: Int { case bottomLeft case bottomCenter case bottomRight - + internal var x: CGFloat { switch self { case .topLeft, .centerLeft, .bottomLeft: return -1.0 @@ -175,7 +176,7 @@ public enum Alignment: Int { case .topRight, .centerRight, .bottomRight: return 1.0 } } - + internal var y: CGFloat { switch self { case .topLeft, .topCenter, .topRight: return -1.0 diff --git a/spine-ios/Sources/Spine/Extensions/MTLClearColor+UIColor.swift b/spine-ios/Sources/Spine/Extensions/MTLClearColor+UIColor.swift index 6fc12a180..f7ab1046c 100644 --- a/spine-ios/Sources/Spine/Extensions/MTLClearColor+UIColor.swift +++ b/spine-ios/Sources/Spine/Extensions/MTLClearColor+UIColor.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import UIKit import MetalKit +import UIKit extension MTLClearColor { init(_ color: UIColor) { @@ -36,9 +36,9 @@ extension MTLClearColor { var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 - + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - + self.init(red: Double(red), green: Double(green), blue: Double(blue), alpha: Double(alpha)) } } diff --git a/spine-ios/Sources/Spine/Extensions/RenderCommand+Vertices.swift b/spine-ios/Sources/Spine/Extensions/RenderCommand+Vertices.swift index 5a16905bb..9230ad005 100644 --- a/spine-ios/Sources/Spine/Extensions/RenderCommand+Vertices.swift +++ b/spine-ios/Sources/Spine/Extensions/RenderCommand+Vertices.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import SpineShadersStructs import Foundation +import SpineShadersStructs import simd extension RenderCommand { @@ -55,7 +55,7 @@ extension RenderCommand { ) vertices.append(vertex) } - + return vertices } diff --git a/spine-ios/Sources/Spine/Extensions/SkeletonDrawableWrapper+CGImage.swift b/spine-ios/Sources/Spine/Extensions/SkeletonDrawableWrapper+CGImage.swift index 9ddb991d4..a6599a19d 100644 --- a/spine-ios/Sources/Spine/Extensions/SkeletonDrawableWrapper+CGImage.swift +++ b/spine-ios/Sources/Spine/Extensions/SkeletonDrawableWrapper+CGImage.swift @@ -27,21 +27,21 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +import CoreGraphics import Foundation import UIKit -import CoreGraphics -public extension SkeletonDrawableWrapper { - +extension SkeletonDrawableWrapper { + /// Render the ``Skeleton`` to a `CGImage` /// /// Parameters: /// - size: The size of the `CGImage` that should be rendered. /// - backgroundColor: the background color of the image /// - scaleFactor: The scale factor. Set this to `UIScreen.main.scale` if you want to show the image in a view - func renderToImage(size: CGSize, backgroundColor: UIColor, scaleFactor: CGFloat = 1) throws -> CGImage? { + public func renderToImage(size: CGSize, backgroundColor: UIColor, scaleFactor: CGFloat = 1) throws -> CGImage? { let spineView = SpineUIView( - controller: SpineController(disposeDrawableOnDeInit: false), // Doesn't own the drawable + controller: SpineController(disposeDrawableOnDeInit: false), // Doesn't own the drawable backgroundColor: backgroundColor ) spineView.frame = CGRect(origin: .zero, size: size) @@ -49,12 +49,12 @@ public extension SkeletonDrawableWrapper { spineView.enableSetNeedsDisplay = false spineView.framebufferOnly = false spineView.contentScaleFactor = scaleFactor - + try spineView.load(drawable: self) spineView.renderer?.waitUntilCompleted = true - + spineView.delegate?.draw(in: spineView) - + guard let texture = spineView.currentDrawable?.texture else { throw SpineError("Could not read texture.") } @@ -65,18 +65,22 @@ public extension SkeletonDrawableWrapper { defer { data.deallocate() } - + let region = MTLRegionMake2D(0, 0, width, height) texture.getBytes(data, bytesPerRow: rowBytes, from: region, mipmapLevel: 0) - + let bitmapInfo = CGBitmapInfo( rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue ).union(.byteOrder32Little) - + let colorSpace = CGColorSpaceCreateDeviceRGB() - guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace, bitmapInfo: bitmapInfo.rawValue), - let cgImage = context.makeImage() else { - throw SpineError("Could not create image.") + guard + let context = CGContext( + data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace, + bitmapInfo: bitmapInfo.rawValue), + let cgImage = context.makeImage() + else { + throw SpineError("Could not create image.") } return cgImage } diff --git a/spine-ios/Sources/Spine/Metal/SpineObjects.swift b/spine-ios/Sources/Spine/Metal/SpineObjects.swift index e6eb87bb6..27b7dd166 100644 --- a/spine-ios/Sources/Spine/Metal/SpineObjects.swift +++ b/spine-ios/Sources/Spine/Metal/SpineObjects.swift @@ -35,13 +35,13 @@ import MetalKit /// Persistent Objects /// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/PersistentObjects.html#//apple_ref/doc/uid/TP40016642-CH3-SW1 internal final class SpineObjects { - + static let shared = SpineObjects() - + internal lazy var device: MTLDevice = { MTLCreateSystemDefaultDevice()! }() - + internal lazy var commandQueue: MTLCommandQueue = { device.makeCommandQueue()! }() diff --git a/spine-ios/Sources/Spine/Metal/SpineRenderer.swift b/spine-ios/Sources/Spine/Metal/SpineRenderer.swift index 0908d7130..7bceca3cb 100644 --- a/spine-ios/Sources/Spine/Metal/SpineRenderer.swift +++ b/spine-ios/Sources/Spine/Metal/SpineRenderer.swift @@ -29,18 +29,18 @@ import Foundation import MetalKit -import SpineShadersStructs import Spine import SpineCppLite +import SpineShadersStructs protocol SpineRendererDelegate: AnyObject { func spineRendererWillUpdate(_ spineRenderer: SpineRenderer) func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval) func spineRendererDidUpdate(_ spineRenderer: SpineRenderer) - + func spineRendererWillDraw(_ spineRenderer: SpineRenderer) func spineRendererDidDraw(_ spineRenderer: SpineRenderer) - + func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize) } @@ -50,11 +50,11 @@ protocol SpineRendererDataSource: AnyObject { } internal final class SpineRenderer: NSObject, MTKViewDelegate { - + private let device: MTLDevice private let textures: [MTLTexture] private let commandQueue: MTLCommandQueue - + private var sizeInPoints: CGSize = .zero private var viewPortSize = vector_uint2(0, 0) private var transform = SpineTransform( @@ -65,17 +65,17 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { internal var lastDraw: CFTimeInterval = 0 internal var waitUntilCompleted = false private var pipelineStatesByBlendMode = [Int: MTLRenderPipelineState]() - + private static let numberOfBuffers = 3 - private static let defaultBufferSize = 32 * 1024 // 32KB - + private static let defaultBufferSize = 32 * 1024 // 32KB + private var buffers = [MTLBuffer]() private let bufferingSemaphore = DispatchSemaphore(value: SpineRenderer.numberOfBuffers) private var currentBufferIndex: Int = 0 - + weak var dataSource: SpineRendererDataSource? weak var delegate: SpineRendererDelegate? - + internal init( device: MTLDevice, commandQueue: MTLCommandQueue, @@ -85,18 +85,19 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { ) throws { self.device = device self.commandQueue = commandQueue - + let bundle: Bundle - #if SWIFT_PACKAGE // SPM - bundle = .module - #else // CocoaPods - let bundleURL = Bundle(for: SpineRenderer.self).url(forResource: "SpineBundle", withExtension: "bundle") - bundle = Bundle(url: bundleURL!)! + #if SWIFT_PACKAGE // SPM + bundle = .module + #else // CocoaPods + let bundleURL = Bundle(for: SpineRenderer.self).url(forResource: "SpineBundle", withExtension: "bundle") + bundle = Bundle(url: bundleURL!)! #endif - + let defaultLibrary = try device.makeDefaultLibrary(bundle: bundle) let textureLoader = MTKTextureLoader(device: device) - textures = try atlasPages + textures = + try atlasPages .compactMap { $0.cgImage } .map { try textureLoader.newTexture( @@ -107,12 +108,12 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { ] ) } - + let blendModes = [ SPINE_BLEND_MODE_NORMAL, SPINE_BLEND_MODE_ADDITIVE, SPINE_BLEND_MODE_MULTIPLY, - SPINE_BLEND_MODE_SCREEN + SPINE_BLEND_MODE_SCREEN, ] for blendMode in blendModes { let descriptor = MTLRenderPipelineDescriptor() @@ -125,15 +126,15 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { ) pipelineStatesByBlendMode[Int(blendMode.rawValue)] = try device.makeRenderPipelineState(descriptor: descriptor) } - + super.init() - + increaseBuffersSize(to: SpineRenderer.defaultBufferSize) } - + func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { guard let spineView = view as? SpineUIView else { return } - + sizeInPoints = CGSize(width: size.width / UIScreen.main.scale, height: size.height / UIScreen.main.scale) viewPortSize = vector_uint2(UInt32(size.width), UInt32(size.height)) setTransform( @@ -142,35 +143,36 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { alignment: spineView.alignment ) } - + func draw(in view: MTKView) { guard dataSource?.isPlaying(self) ?? false else { lastDraw = CACurrentMediaTime() return } - + callNeedsUpdate() - + // Tripple Buffering // Source: https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html#//apple_ref/doc/uid/TP40016642-CH5-SW1 bufferingSemaphore.wait() currentBufferIndex = (currentBufferIndex + 1) % SpineRenderer.numberOfBuffers - + guard let renderCommands = dataSource?.renderCommands(self), - let commandBuffer = commandQueue.makeCommandBuffer(), - let renderPassDescriptor = view.currentRenderPassDescriptor, - let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - // this can happen if, - // - CAMetalLayer is configured with drawable timeout, and CAMetalLayer is run out of Drawable - // - CAMetalLayer is added to the window with frame size of zero or incorrect layout constraint -> currentRenderPassDescriptor is null + let commandBuffer = commandQueue.makeCommandBuffer(), + let renderPassDescriptor = view.currentRenderPassDescriptor, + let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) + else { + // this can happen if, + // - CAMetalLayer is configured with drawable timeout, and CAMetalLayer is run out of Drawable + // - CAMetalLayer is added to the window with frame size of zero or incorrect layout constraint -> currentRenderPassDescriptor is null bufferingSemaphore.signal() return } - + delegate?.spineRendererWillDraw(self) draw(renderCommands: renderCommands, renderEncoder: renderEncoder, in: view) delegate?.spineRendererDidDraw(self) - + renderEncoder.endEncoding() view.currentDrawable.flatMap { commandBuffer.present($0) @@ -183,14 +185,14 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { commandBuffer.waitUntilCompleted() } } - + private func setTransform(bounds: CGRect, mode: Spine.ContentMode, alignment: Spine.Alignment) { let x = -bounds.minX - bounds.width / 2.0 let y = -bounds.minY - bounds.height / 2.0 - + var scaleX: CGFloat = 1.0 var scaleY: CGFloat = 1.0 - + switch mode { case .fit: scaleX = min(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height) @@ -199,16 +201,16 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { scaleX = max(sizeInPoints.width / bounds.width, sizeInPoints.height / bounds.height) scaleY = scaleX } - + let offsetX = abs(sizeInPoints.width - bounds.width * scaleX) / 2 * alignment.x let offsetY = abs(sizeInPoints.height - bounds.height * scaleY) / 2 * alignment.y - + transform = SpineTransform( translation: vector_float2(Float(x), Float(y)), scale: vector_float2(Float(scaleX * UIScreen.main.scale), Float(scaleY * UIScreen.main.scale)), offset: vector_float2(Float(offsetX * UIScreen.main.scale), Float(offsetY * UIScreen.main.scale)) ) - + delegate?.spineRendererDidUpdate( self, scaleX: scaleX, @@ -218,37 +220,37 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { size: sizeInPoints ) } - + private func callNeedsUpdate() { if lastDraw == 0 { lastDraw = CACurrentMediaTime() } let delta = CACurrentMediaTime() - lastDraw - delegate?.spineRendererWillUpdate(self) + delegate?.spineRendererWillUpdate(self) delegate?.spineRenderer(self, needsUpdate: delta) lastDraw = CACurrentMediaTime() delegate?.spineRendererDidUpdate(self) } - + private func draw(renderCommands: [RenderCommand], renderEncoder: MTLRenderCommandEncoder, in view: MTKView) { let allVertices = renderCommands.map { renderCommand in Array(renderCommand.getVertices()) } let vertices = allVertices.flatMap { $0 } let verticesSize = MemoryLayout.stride * vertices.count - + guard verticesSize > 0 else { return } - + var vertexBuffer = buffers[currentBufferIndex] var vertexBufferSize = vertexBuffer.length - + if vertexBufferSize < verticesSize { increaseBuffersSize(to: verticesSize) vertexBuffer = buffers[currentBufferIndex] } - + renderEncoder.setViewport( MTLViewport( originX: 0.0, @@ -259,9 +261,9 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { zfar: 1.0 ) ) - + memcpy(vertexBuffer.contents(), vertices, verticesSize) - + renderEncoder.setVertexBuffer( vertexBuffer, offset: 0, @@ -277,7 +279,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { length: MemoryLayout.size(ofValue: viewPortSize), index: Int(SpineVertexInputIndexViewportSize.rawValue) ) - + // Buffer Bindings // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/BufferBindings.html#//apple_ref/doc/uid/TP40016642-CH28-SW3 var vertexStart = 0 @@ -286,9 +288,9 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { continue } renderEncoder.setRenderPipelineState(pipelineState) - + let vertices = allVertices[index] - + let textureIndex = Int(renderCommand.atlasPage) if textures.indices.contains(textureIndex) { renderEncoder.setFragmentTexture( @@ -296,7 +298,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { index: Int(SpineTextureIndexBaseColor.rawValue) ) } - + renderEncoder.drawPrimitives( type: .triangle, vertexStart: vertexStart, @@ -305,89 +307,89 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate { vertexStart += vertices.count } } - + private func getPipelineState(blendMode: BlendMode) -> MTLRenderPipelineState? { pipelineStatesByBlendMode[Int(blendMode.rawValue)] } - + private func increaseBuffersSize(to size: Int) { - buffers = (0 ..< SpineRenderer.numberOfBuffers).map { _ in + buffers = (0.. MTLBlendFactor { - switch self { - case SPINE_BLEND_MODE_NORMAL: - return premultipliedAlpha ? .one : .sourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: - // additvie only needs sourceAlpha multiply if it is not pma - return premultipliedAlpha ? .one : .sourceAlpha - case SPINE_BLEND_MODE_MULTIPLY: - return .destinationColor - case SPINE_BLEND_MODE_SCREEN: - return .one - default: - return .one // Should never be called - } - } - - var sourceAlphaBlendFactor: MTLBlendFactor { - // pma and non-pma has no-relation ship with alpha blending - switch self { - case SPINE_BLEND_MODE_NORMAL: - return .one - case SPINE_BLEND_MODE_ADDITIVE: - return .one - case SPINE_BLEND_MODE_MULTIPLY: - return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: - return .oneMinusSourceColor - default: - return .one // Should never be called - } - } +extension BlendMode { + fileprivate func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor { + switch self { + case SPINE_BLEND_MODE_NORMAL: + return premultipliedAlpha ? .one : .sourceAlpha + case SPINE_BLEND_MODE_ADDITIVE: + // additvie only needs sourceAlpha multiply if it is not pma + return premultipliedAlpha ? .one : .sourceAlpha + case SPINE_BLEND_MODE_MULTIPLY: + return .destinationColor + case SPINE_BLEND_MODE_SCREEN: + return .one + default: + return .one // Should never be called + } + } - var destinationRGBBlendFactor: MTLBlendFactor { - switch self { - case SPINE_BLEND_MODE_NORMAL: - return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: - return .one - case SPINE_BLEND_MODE_MULTIPLY: - return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: - return .oneMinusSourceColor - default: - return .one // Should never be called - } - } + fileprivate var sourceAlphaBlendFactor: MTLBlendFactor { + // pma and non-pma has no-relation ship with alpha blending + switch self { + case SPINE_BLEND_MODE_NORMAL: + return .one + case SPINE_BLEND_MODE_ADDITIVE: + return .one + case SPINE_BLEND_MODE_MULTIPLY: + return .oneMinusSourceAlpha + case SPINE_BLEND_MODE_SCREEN: + return .oneMinusSourceColor + default: + return .one // Should never be called + } + } - var destinationAlphaBlendFactor: MTLBlendFactor { - switch self { - case SPINE_BLEND_MODE_NORMAL: - return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_ADDITIVE: - return .one - case SPINE_BLEND_MODE_MULTIPLY: - return .oneMinusSourceAlpha - case SPINE_BLEND_MODE_SCREEN: - return .oneMinusSourceColor - default: - return .one // Should never be called - } - } + fileprivate var destinationRGBBlendFactor: MTLBlendFactor { + switch self { + case SPINE_BLEND_MODE_NORMAL: + return .oneMinusSourceAlpha + case SPINE_BLEND_MODE_ADDITIVE: + return .one + case SPINE_BLEND_MODE_MULTIPLY: + return .oneMinusSourceAlpha + case SPINE_BLEND_MODE_SCREEN: + return .oneMinusSourceColor + default: + return .one // Should never be called + } + } + + fileprivate var destinationAlphaBlendFactor: MTLBlendFactor { + switch self { + case SPINE_BLEND_MODE_NORMAL: + return .oneMinusSourceAlpha + case SPINE_BLEND_MODE_ADDITIVE: + return .one + case SPINE_BLEND_MODE_MULTIPLY: + return .oneMinusSourceAlpha + case SPINE_BLEND_MODE_SCREEN: + return .oneMinusSourceColor + default: + return .one // Should never be called + } + } } -fileprivate extension MTLRenderPipelineColorAttachmentDescriptor { - - func apply(blendMode: BlendMode, with premultipliedAlpha: Bool) { - isBlendingEnabled = true - sourceRGBBlendFactor = blendMode.sourceRGBBlendFactor(premultipliedAlpha: premultipliedAlpha) - sourceAlphaBlendFactor = blendMode.sourceAlphaBlendFactor - destinationRGBBlendFactor = blendMode.destinationRGBBlendFactor - destinationAlphaBlendFactor = blendMode.destinationAlphaBlendFactor - } +extension MTLRenderPipelineColorAttachmentDescriptor { + + fileprivate func apply(blendMode: BlendMode, with premultipliedAlpha: Bool) { + isBlendingEnabled = true + sourceRGBBlendFactor = blendMode.sourceRGBBlendFactor(premultipliedAlpha: premultipliedAlpha) + sourceAlphaBlendFactor = blendMode.sourceAlphaBlendFactor + destinationRGBBlendFactor = blendMode.destinationRGBBlendFactor + destinationAlphaBlendFactor = blendMode.destinationAlphaBlendFactor + } } diff --git a/spine-ios/Sources/Spine/SkeletonDrawableWrapper.swift b/spine-ios/Sources/Spine/SkeletonDrawableWrapper.swift index 08085c61a..cdb8d7046 100644 --- a/spine-ios/Sources/Spine/SkeletonDrawableWrapper.swift +++ b/spine-ios/Sources/Spine/SkeletonDrawableWrapper.swift @@ -27,10 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +import CoreGraphics import Foundation import Spine import SpineCppLite -import CoreGraphics import UIKit /// A ``SkeletonDrawableWrapper`` with ``SkeletonDrawable`` bundle loading, updating, and rendering an ``Atlas``, ``Skeleton``, and ``AnimationState`` @@ -54,19 +54,19 @@ import UIKit @objc(SpineSkeletonDrawableWrapper) @objcMembers public final class SkeletonDrawableWrapper: NSObject { - + public let atlas: Atlas public let atlasPages: [UIImage] public let skeletonData: SkeletonData - + public let skeletonDrawable: SkeletonDrawable public let skeleton: Skeleton public let animationStateData: AnimationStateData public let animationState: AnimationState public let animationStateWrapper: AnimationStateWrapper - + internal var disposed = false - + /// Constructs a new skeleton drawable from the `atlasFileName` and `skeletonFileName` from the `main` bundle /// or the optionally provided `bundle`. /// @@ -84,7 +84,7 @@ public final class SkeletonDrawableWrapper: NSObject { skeletonData: skeletonData ) } - + /// Constructs a new skeleton drawable from the `atlasFile` and `skeletonFile`. /// /// Throws an `Error` in case the data could not be loaded. @@ -100,7 +100,7 @@ public final class SkeletonDrawableWrapper: NSObject { skeletonData: skeletonData ) } - + /// Constructs a new skeleton drawable wrapper from the http `atlasUrl` and `skeletonUrl`. /// /// Throws an `Error` in case the data could not be loaded. @@ -116,27 +116,27 @@ public final class SkeletonDrawableWrapper: NSObject { skeletonData: skeletonData ) } - + public init(atlas: Atlas, atlasPages: [UIImage], skeletonData: SkeletonData) throws { self.atlas = atlas self.atlasPages = atlasPages self.skeletonData = skeletonData - + guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else { throw SpineError("Could not load native skeleton drawable") } skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable) - + guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else { throw SpineError("Could not load native skeleton") } skeleton = Skeleton(nativeSkeleton) - + guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else { throw SpineError("Could not load native animation state data") } animationStateData = AnimationStateData(nativeAnimationStateData) - + guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else { throw SpineError("Could not load native animation state") } @@ -148,27 +148,27 @@ public final class SkeletonDrawableWrapper: NSObject { skeleton.updateWorldTransform(physics: SPINE_PHYSICS_NONE) super.init() } - + /// Updates the ``AnimationState`` using the `delta` time given in seconds, applies the /// animation state to the ``Skeleton`` and updates the world transforms of the skeleton /// to calculate its current pose. public func update(delta: Float) { if disposed { return } - + animationStateWrapper.update(delta: delta) animationState.apply(skeleton: skeleton) - + skeleton.update(delta: delta) skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE) } - + public func dispose() { if disposed { return } disposed = true - + atlas.dispose() skeletonData.dispose() - + skeletonDrawable.dispose() } } diff --git a/spine-ios/Sources/Spine/Spine.Generated+Extensions.swift b/spine-ios/Sources/Spine/Spine.Generated+Extensions.swift index 9793ad660..2b2808e70 100644 --- a/spine-ios/Sources/Spine/Spine.Generated+Extensions.swift +++ b/spine-ios/Sources/Spine/Spine.Generated+Extensions.swift @@ -28,8 +28,8 @@ *****************************************************************************/ import Foundation -import SwiftUI import SpineCppLite +import SwiftUI public var version: String { return "\(majorVersion).\(minorVersion)" @@ -49,22 +49,22 @@ public var minorVersion: Int { /// /// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()` /// when the atlas is no longer in use to release its resources. -public extension Atlas { - +extension Atlas { + /// Loads an ``Atlas`` from the file with name `atlasFileName` in the `main` bundle or the optionally provided [bundle]. /// /// Throws an `Error` in case the atlas could not be loaded. - static func fromBundle(_ atlasFileName: String, bundle: Bundle = .main) async throws -> (Atlas, [UIImage]) { + public static func fromBundle(_ atlasFileName: String, bundle: Bundle = .main) async throws -> (Atlas, [UIImage]) { let data = try await FileSource.bundle(fileName: atlasFileName, bundle: bundle).load() return try await Self.fromData(data: data) { name in return try await FileSource.bundle(fileName: name, bundle: bundle).load() } } - + /// Loads an ``Atlas`` from the file URL `atlasFile`. /// /// Throws an `Error` in case the atlas could not be loaded. - static func fromFile(_ atlasFile: URL) async throws -> (Atlas, [UIImage]) { + public static func fromFile(_ atlasFile: URL) async throws -> (Atlas, [UIImage]) { let data = try await FileSource.file(atlasFile).load() return try await Self.fromData(data: data) { name in let dir = atlasFile.deletingLastPathComponent() @@ -72,11 +72,11 @@ public extension Atlas { return try await FileSource.file(file).load() } } - + /// Loads an ``Atlas`` from the http URL `atlasURL`. /// /// Throws an `Error` in case the atlas could not be loaded. - static func fromHttp(_ atlasURL: URL) async throws -> (Atlas, [UIImage]) { + public static func fromHttp(_ atlasURL: URL) async throws -> (Atlas, [UIImage]) { let data = try await FileSource.http(atlasURL).load() return try await Self.fromData(data: data) { name in let dir = atlasURL.deletingLastPathComponent() @@ -84,7 +84,7 @@ public extension Atlas { return try await FileSource.http(file).load() } } - + private static func fromData(data: Data, loadFile: (_ name: String) async throws -> Data) async throws -> (Atlas, [UIImage]) { guard let atlasData = String(data: data, encoding: .utf8) else { throw SpineError("Couldn't read atlas bytes as utf8 string") @@ -100,10 +100,10 @@ public extension Atlas { spine_atlas_dispose(atlas) throw SpineError("Couldn't load atlas: \(message)") } - + var atlasPages = [UIImage]() - let numImagePaths = spine_atlas_get_num_image_paths(atlas); - + let numImagePaths = spine_atlas_get_num_image_paths(atlas) + for i in 0.. SkeletonData { + public static func fromBundle(atlas: Atlas, skeletonFileName: String, bundle: Bundle = .main) async throws -> SkeletonData { return try fromData( atlas: atlas, data: try await FileSource.bundle(fileName: skeletonFileName, bundle: bundle).load(), isJson: skeletonFileName.hasSuffix(".json") ) } - + /// Loads a ``SkeletonData`` from the file URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images. /// /// Throws an `Error` in case the skeleton data could not be loaded. - static func fromFile(atlas: Atlas, skeletonFile: URL) async throws -> SkeletonData { + public static func fromFile(atlas: Atlas, skeletonFile: URL) async throws -> SkeletonData { return try fromData( atlas: atlas, data: try await FileSource.file(skeletonFile).load(), isJson: skeletonFile.absoluteString.hasSuffix(".json") ) } - + /// Loads a ``SkeletonData`` from the http URL `skeletonFile`. Uses the provided ``Atlas`` to resolve attachment images. /// /// Throws an `Error` in case the skeleton data could not be loaded. - static func fromHttp(atlas: Atlas, skeletonURL: URL) async throws -> SkeletonData { + public static func fromHttp(atlas: Atlas, skeletonURL: URL) async throws -> SkeletonData { return try fromData( atlas: atlas, data: try await FileSource.http(skeletonURL).load(), isJson: skeletonURL.absoluteString.hasSuffix(".json") ) } - + /// Loads a ``SkeletonData`` from the ``binary`` skeleton `Data`, using the provided ``Atlas`` to resolve attachment images. /// /// Throws an `Error` in case the skeleton data could not be loaded. - static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData { - let result = try data.withUnsafeBytes{ + public static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData { + let result = try data.withUnsafeBytes { try $0.withMemoryRebound(to: UInt8.self) { buffer in guard let ptr = buffer.baseAddress else { throw SpineError("Couldn't read atlas binary") @@ -187,16 +187,17 @@ public extension SkeletonData { } return SkeletonData(data) } - + /// Loads a ``SkeletonData`` from the `json` string, using the provided ``Atlas`` to resolve attachment /// images. /// /// Throws an `Error` in case the atlas could not be loaded. - static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData { + public static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData { let result = try json.utf8CString.withUnsafeBufferPointer { buffer in guard let basePtr = buffer.baseAddress, - let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr) else { + let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr) + else { throw SpineError("Couldn't load skeleton data json") } return result @@ -213,7 +214,7 @@ public extension SkeletonData { } return SkeletonData(data) } - + private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData { if isJson { guard let json = String(data: data, encoding: .utf8) else { @@ -226,12 +227,12 @@ public extension SkeletonData { } } -internal extension SkeletonDrawable { - +extension SkeletonDrawable { + func render() -> [RenderCommand] { var commands = [RenderCommand]() if disposed { return commands } - + var nativeCmd = spine_skeleton_drawable_render(wrappee) repeat { if let ncmd = nativeCmd { @@ -240,18 +241,18 @@ internal extension SkeletonDrawable { } else { nativeCmd = nil } - } while (nativeCmd != nil) - + } while nativeCmd != nil + return commands } } -internal extension RenderCommand { - +extension RenderCommand { + var numVertices: Int { Int(spine_render_command_get_num_vertices(wrappee)) } - + func positions(numVertices: Int) -> [Float] { let num = numVertices * 2 let ptr = spine_render_command_get_positions(wrappee) @@ -259,7 +260,7 @@ internal extension RenderCommand { let buffer = UnsafeBufferPointer(start: validPtr, count: num) return Array(buffer) } - + func uvs(numVertices: Int) -> [Float] { let num = numVertices * 2 let ptr = spine_render_command_get_uvs(wrappee) @@ -267,8 +268,8 @@ internal extension RenderCommand { let buffer = UnsafeBufferPointer(start: validPtr, count: num) return Array(buffer) } - - func colors(numVertices: Int) ->[Int32] { + + func colors(numVertices: Int) -> [Int32] { let num = numVertices let ptr = spine_render_command_get_colors(wrappee) guard let validPtr = ptr else { return [] } @@ -277,21 +278,21 @@ internal extension RenderCommand { } } -public extension Skin { - +extension Skin { + /// Constructs a new empty ``Skin`` using the given `name`. Skins constructed this way must be manually disposed via the `dispose` method /// if they are no longer used. - static func create(name: String) -> Skin { + public static func create(name: String) -> Skin { return Skin(spine_skin_create(name)) } } // Helper -public extension CGRect { - +extension CGRect { + /// Construct a `CGRect` from ``Bounds`` - init(bounds: Bounds) { + public init(bounds: Bounds) { self = CGRect( x: CGFloat(bounds.x), y: CGFloat(bounds.y), @@ -305,7 +306,7 @@ internal enum FileSource { case bundle(fileName: String, bundle: Bundle = .main) case file(URL) case http(URL) - + internal func load() async throws -> Data { switch self { case .bundle(let fileName, let bundle): @@ -314,7 +315,7 @@ internal enum FileSource { throw SpineError("Provide both file name and file extension") } let name = components.dropLast(1).joined(separator: ".") - + guard let fileUrl = bundle.url(forResource: name, withExtension: String(ext)) else { throw SpineError("Could not load file with name \(name) from bundle") } @@ -331,9 +332,9 @@ internal enum FileSource { } else { let lock = NSRecursiveLock() nonisolated(unsafe) - var isCancelled = false + var isCancelled = false nonisolated(unsafe) - var taskHolder:URLSessionDownloadTask? = nil + var taskHolder: URLSessionDownloadTask? = nil return try await withTaskCancellationHandler { try await withCheckedThrowingContinuation { continuation in let task = URLSession.shared.downloadTask(with: url) { temp, response, error in @@ -380,25 +381,25 @@ internal enum FileSource { } public struct SpineError: Error, CustomStringConvertible { - + public let description: String - + internal init(_ description: String) { self.description = description } - + } -public extension SkeletonBounds { - static func create() -> SkeletonBounds { +extension SkeletonBounds { + public static func create() -> SkeletonBounds { return SkeletonBounds(spine_skeleton_bounds_create()) } } -@objc public extension Atlas { - - var imagePathCount:Int32 { +@objc extension Atlas { + + public var imagePathCount: Int32 { spine_atlas_get_num_image_paths(wrappee) } - + } diff --git a/spine-ios/Sources/Spine/SpineController.swift b/spine-ios/Sources/Spine/SpineController.swift index dab6189ed..c1081c158 100644 --- a/spine-ios/Sources/Spine/SpineController.swift +++ b/spine-ios/Sources/Spine/SpineController.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import Foundation import CoreGraphics +import Foundation import QuartzCore import UIKit @@ -63,27 +63,27 @@ public typealias SpineControllerCallback = (_ controller: SpineController) -> Vo /// Per default, ``SkeletonDrawableWrapper`` is disposed when ``SpineController`` is deinitialized. You can disable this behaviour with the ``disposeDrawableOnDeInit`` contructor parameter. @objcMembers public final class SpineController: NSObject, ObservableObject { - + public internal(set) var drawable: SkeletonDrawableWrapper! - + private let onInitialized: SpineControllerCallback? private let onBeforeUpdateWorldTransforms: SpineControllerCallback? private let onAfterUpdateWorldTransforms: SpineControllerCallback? private let onBeforePaint: SpineControllerCallback? private let onAfterPaint: SpineControllerCallback? private let disposeDrawableOnDeInit: Bool - + private var scaleX: CGFloat = 1 private var scaleY: CGFloat = 1 private var offsetX: CGFloat = 0 private var offsetY: CGFloat = 0 - + @Published public private(set) var isPlaying: Bool = true - + @Published public private(set) var viewSize: CGSize = .zero - + /// Constructs a new ``SpineUIview`` controller. See the class documentation of ``SpineWidgetController`` for information on /// the optional arguments. public init( @@ -100,80 +100,80 @@ public final class SpineController: NSObject, ObservableObject { self.onBeforePaint = onBeforePaint self.onAfterPaint = onAfterPaint self.disposeDrawableOnDeInit = disposeDrawableOnDeInit - + super.init() } - + deinit { if disposeDrawableOnDeInit { - drawable?.dispose() // TODO move drawable out of view? + drawable?.dispose() // TODO move drawable out of view? } } - + /// The ``Atlas`` from which images to render the skeleton are sourced. public var atlas: Atlas { drawable.atlas } - + /// The setup-pose data used by the skeleton. public var skeletonData: SkeletonData { drawable.skeletonData } - + /// The ``Skeleton`` public var skeleton: Skeleton { drawable.skeleton } - + /// The mixing information used by the ``AnimationState`` public var animationStateData: AnimationStateData { drawable.animationStateData } - + /// The ``AnimationState`` used to manage animations that are being applied to the /// skeleton. public var animationState: AnimationState { drawable.animationState } - + /// The ``AnimationStateWrapper`` used to hold ``AnimationState``, register ``AnimationStateListener`` and call ``AnimationStateWrapper/update(delta:)`` public var animationStateWrapper: AnimationStateWrapper { drawable.animationStateWrapper } - + /// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to /// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this /// to move a bone based on user touch input. public func toSkeletonCoordinates(position: CGPoint) -> CGPoint { - let x = position.x; - let y = position.y; + let x = position.x + let y = position.y return CGPoint( x: (x - viewSize.width / 2) / scaleX - offsetX, y: (y - viewSize.height / 2) / scaleY - offsetY ) } - + /// Transforms the coordinates given in skeleton coordinate system to /// the the ``SpineUIView`` coordinates. See the `DebugRendering.swift` example hot to use this to draw rectangles over skeleton bones for debugging purposes. public func fromSkeletonCoordinates(position: CGPoint) -> CGPoint { - let x = position.x; - let y = position.y; + let x = position.x + let y = position.y return CGPoint( x: (x + offsetX) * scaleX, y: (y + offsetY) * scaleY ) } - + /// Pauses updating and rendering the skeleton. public func pause() { isPlaying = false } - + /// Resumes updating and rendering the skeleton. public func resume() { isPlaying = true } - + internal func load(atlasFile: String, skeletonFile: String, bundle: Bundle = .main) async throws { let atlasAndPages = try await Atlas.fromBundle(atlasFile, bundle: bundle) let skeletonData = try await SkeletonData.fromBundle( @@ -190,23 +190,23 @@ public final class SpineController: NSObject, ObservableObject { self.drawable = skeletonDrawableWrapper } } - + internal func initialize() { onInitialized?(self) } - + } extension SpineController: SpineRendererDelegate { - + func spineRendererWillDraw(_ spineRenderer: SpineRenderer) { onBeforePaint?(self) } - + func spineRendererDidDraw(_ spineRenderer: SpineRenderer) { onAfterPaint?(self) } - + func spineRendererDidUpdate(_ spineRenderer: SpineRenderer, scaleX: CGFloat, scaleY: CGFloat, offsetX: CGFloat, offsetY: CGFloat, size: CGSize) { self.scaleX = scaleX self.scaleY = scaleY @@ -217,27 +217,27 @@ extension SpineController: SpineRendererDelegate { } extension SpineController: SpineRendererDataSource { - + func spineRendererWillUpdate(_ spineRenderer: SpineRenderer) { onBeforeUpdateWorldTransforms?(self) } - + func spineRendererDidUpdate(_ spineRenderer: SpineRenderer) { onAfterUpdateWorldTransforms?(self) } - + func spineRenderer(_ spineRenderer: SpineRenderer, needsUpdate delta: TimeInterval) { drawable?.update(delta: Float(delta)) } - + func isPlaying(_ spineRenderer: SpineRenderer) -> Bool { return isPlaying } - + func skeletonDrawable(_ spineRenderer: SpineRenderer) -> SkeletonDrawableWrapper { return drawable } - + func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] { return drawable?.skeletonDrawable.render() ?? [] } diff --git a/spine-ios/Sources/Spine/SpineUIView.swift b/spine-ios/Sources/Spine/SpineUIView.swift index dd104ab85..93b26878a 100644 --- a/spine-ios/Sources/Spine/SpineUIView.swift +++ b/spine-ios/Sources/Spine/SpineUIView.swift @@ -27,8 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import UIKit import MetalKit +import UIKit /// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``. /// @@ -40,15 +40,15 @@ import MetalKit /// This is a direct subclass of ``MTKView`` and is using `Metal` to render the skeleton. @objc public final class SpineUIView: MTKView { - + let controller: SpineController let mode: Spine.ContentMode let alignment: Spine.Alignment let boundsProvider: BoundsProvider - + internal var computedBounds: CGRect = .zero internal var renderer: SpineRenderer? - + @objc internal init( controller: SpineController = SpineController(), mode: Spine.ContentMode = .fit, @@ -60,12 +60,12 @@ public final class SpineUIView: MTKView { self.mode = mode self.alignment = alignment self.boundsProvider = boundsProvider - + super.init(frame: .zero, device: SpineObjects.shared.device) clearColor = MTLClearColor(backgroundColor) isOpaque = backgroundColor != .clear } - + /// An initializer that constructs a new ``SpineUIView`` from a ``SpineViewSource``. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -98,7 +98,7 @@ public final class SpineUIView: MTKView { } } } - + /// A convenience initializer that constructs a new ``SpineUIView`` from bundled files. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -125,9 +125,11 @@ public final class SpineUIView: MTKView { boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { - self.init(from: .bundle(atlasFileName: atlasFileName, skeletonFileName: skeletonFileName, bundle: bundle), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor) + self.init( + from: .bundle(atlasFileName: atlasFileName, skeletonFileName: skeletonFileName, bundle: bundle), controller: controller, mode: mode, + alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor) } - + /// A convenience initializer that constructs a new ``SpineUIView`` from file URLs. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -152,9 +154,11 @@ public final class SpineUIView: MTKView { boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { - self.init(from: .file(atlasFile: atlasFile, skeletonFile: skeletonFile), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor) + self.init( + from: .file(atlasFile: atlasFile, skeletonFile: skeletonFile), controller: controller, mode: mode, alignment: alignment, + boundsProvider: boundsProvider, backgroundColor: backgroundColor) } - + /// A convenience initializer that constructs a new ``SpineUIView`` from HTTP. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -179,9 +183,11 @@ public final class SpineUIView: MTKView { boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { - self.init(from: .http(atlasURL: atlasURL, skeletonURL: skeletonURL), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor) + self.init( + from: .http(atlasURL: atlasURL, skeletonURL: skeletonURL), controller: controller, mode: mode, alignment: alignment, + boundsProvider: boundsProvider, backgroundColor: backgroundColor) } - + /// A convenience initializer that constructs a new ``SpineUIView`` with a ``SkeletonDrawableWrapper``. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -203,17 +209,19 @@ public final class SpineUIView: MTKView { boundsProvider: BoundsProvider = SetupPoseBounds(), backgroundColor: UIColor = .clear ) { - self.init(from: .drawable(drawable), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, backgroundColor: backgroundColor) + self.init( + from: .drawable(drawable), controller: controller, mode: mode, alignment: alignment, boundsProvider: boundsProvider, + backgroundColor: backgroundColor) } - + internal override init(frame frameRect: CGRect, device: MTLDevice?) { fatalError("init(frame: device:) has not been implemented. Use init() instead.") } - + internal required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented. Use init() instead.") } - + /// Disable or enable rendering. Disable it when the spine view is out of bounds and you want to preserve CPU/GPU resources. public var isRendering: Bool { get { !super.isPaused } @@ -227,7 +235,7 @@ public final class SpineUIView: MTKView { } extension SpineUIView { - + internal func load(drawable: SkeletonDrawableWrapper) throws { controller.drawable = drawable computedBounds = boundsProvider.computeBounds(for: drawable) @@ -236,7 +244,7 @@ extension SpineUIView { ) controller.initialize() } - + private func initRenderer(atlasPages: [UIImage]) throws { renderer = try SpineRenderer( device: SpineObjects.shared.device, @@ -265,7 +273,7 @@ public enum SpineViewSource { case file(atlasFile: URL, skeletonFile: URL) case http(atlasURL: URL, skeletonURL: URL) case drawable(SkeletonDrawableWrapper) - + internal func loadDrawable() async throws -> SkeletonDrawableWrapper { switch self { case .bundle(let atlasFileName, let skeletonFileName, let bundle): diff --git a/spine-ios/Sources/Spine/SpineView.swift b/spine-ios/Sources/Spine/SpineView.swift index 0c613d6ce..ec79fe38d 100644 --- a/spine-ios/Sources/Spine/SpineView.swift +++ b/spine-ios/Sources/Spine/SpineView.swift @@ -38,7 +38,7 @@ import SwiftUI /// /// This is a ``UIViewRepresentable`` of `SpineUIView`. public struct SpineView: UIViewRepresentable { - + public typealias UIViewType = SpineUIView private let source: SpineViewSource @@ -46,11 +46,11 @@ public struct SpineView: UIViewRepresentable { private let mode: Spine.ContentMode private let alignment: Spine.Alignment private let boundsProvider: BoundsProvider - private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work. - + private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work. + @Binding private var isRendering: Bool? - + /// An initializer that constructs a new ``SpineView`` from a ``SpineViewSource``. /// /// After initialization is complete, the provided `controller` is invoked as per the ``SpineController`` semantics, to allow @@ -85,7 +85,7 @@ public struct SpineView: UIViewRepresentable { self.backgroundColor = backgroundColor _isRendering = isRendering } - + public func makeUIView(context: Context) -> SpineUIView { return SpineUIView( from: source, @@ -96,7 +96,7 @@ public struct SpineView: UIViewRepresentable { backgroundColor: backgroundColor ) } - + public func updateUIView(_ uiView: SpineUIView, context: Context) { if let isRendering { uiView.isRendering = isRendering diff --git a/spine-ios/Sources/SpineModule/SpineModule.swift b/spine-ios/Sources/SpineModule/SpineModule.swift index 482e50223..c37c63ff6 100644 --- a/spine-ios/Sources/SpineModule/SpineModule.swift +++ b/spine-ios/Sources/SpineModule/SpineModule.swift @@ -6,9 +6,9 @@ // -#if canImport(Spine) -@_exported import Spine -#endif - @_exported import SpineCppLite @_exported import SpineShadersStructs + +#if canImport(Spine) + @_exported import Spine +#endif diff --git a/spine-ts/build.sh b/spine-ts/build.sh index 09722da24..22f165827 100755 --- a/spine-ts/build.sh +++ b/spine-ts/build.sh @@ -16,21 +16,19 @@ fi log_title "Spine-TS Build" log_detail "Branch: $BRANCH" -log_section "Setup" log_action "Installing dependencies" if npm install > /tmp/npm-install.log 2>&1; then - log_ok "Dependencies installed" + log_ok else - log_fail "npm install failed" - log_detail "$(cat /tmp/npm-install.log)" + log_fail + log_error_output "$(cat /tmp/npm-install.log)" exit 1 fi if ! [ -z "$TS_UPDATE_URL" ] && ! [ -z "$BRANCH" ]; then - log_section "Deploy" log_action "Creating artifacts zip" - zip -j spine-ts.zip \ + if ZIP_OUTPUT=$(zip -j spine-ts.zip \ spine-core/dist/iife/* \ spine-canvas/dist/iife/* \ spine-webgl/dist/iife/* \ @@ -51,13 +49,20 @@ then spine-phaser-v3/dist/esm/* \ spine-phaser-v4/dist/esm/* \ spine-webcomponents/dist/esm/* \ - spine-player/css/spine-player.css > /dev/null 2>&1 + spine-player/css/spine-player.css 2>&1); then + log_ok + else + log_fail + log_error_output "$ZIP_OUTPUT" + exit 1 + fi log_action "Uploading to $TS_UPDATE_URL$BRANCH" - if curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH" > /dev/null 2>&1; then - log_ok "Artifacts deployed" + if CURL_OUTPUT=$(curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH" 2>&1); then + log_ok else - log_fail "Upload failed" + log_fail + log_error_output "$CURL_OUTPUT" exit 1 fi diff --git a/tests/generate-serializers.sh b/tests/generate-serializers.sh index 6748df5d1..dea771bb2 100755 --- a/tests/generate-serializers.sh +++ b/tests/generate-serializers.sh @@ -17,7 +17,6 @@ fi # Install dependencies if node_modules doesn't exist if [ ! -d "node_modules" ]; then - log_section "Setup" log_action "Installing dependencies" if npm install > /tmp/npm-install.log 2>&1; then log_ok "Dependencies installed" @@ -28,51 +27,42 @@ if [ ! -d "node_modules" ]; then fi fi -log_section "Analyzing API" log_action "Analyzing Java API" if output=$(npx -y tsx src/analyze-java-api.ts 2>&1); then - log_ok "Java API analysis completed" + log_ok else - log_fail "Failed to analyze Java API" + log_fail log_detail "$output" exit 1 fi -log_section "Generating Serializer IR" log_action "Generating intermediate representation" if output=$(npx -y tsx src/generate-serializer-ir.ts 2>&1); then - log_ok "Serializer IR generated successfully" + log_ok else - log_fail "Failed to generate serializer IR" + log_fail log_detail "$output" exit 1 fi -log_section "Generating Language-Specific Serializers" log_action "Generating Java SkeletonSerializer" if output=$(npx -y tsx src/generate-java-serializer.ts 2>&1); then - log_ok "Java serializer generated successfully" - - log_action "Formatting Java code" - ../formatters/format.sh java - log_ok "Java code formatted" + log_ok else log_fail "Failed to generate Java serializer" log_detail "$output" exit 1 fi +../formatters/format-java.sh log_action "Generating C++ SkeletonSerializer" if output=$(npx -y tsx src/generate-cpp-serializer.ts 2>&1); then log_ok "C++ serializer generated successfully" - - log_action "Formatting C++ code" - ../formatters/format.sh cpp - log_ok "C++ code formatted" else log_fail "Failed to generate C++ serializer" log_detail "$output" exit 1 fi +../formatters/format-cpp.sh -log_summary "✓ Serializer generation and formatting completed successfully" \ No newline at end of file +log_summary "✓ Serializer generation completed successfully" \ No newline at end of file diff --git a/tests/test.sh b/tests/test.sh index fa7627707..d2e8c97eb 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -16,18 +16,16 @@ fi # Install dependencies if node_modules doesn't exist if [ ! -d "node_modules" ]; then - log_section "Setup" log_action "Installing dependencies" if npm install > /tmp/npm-install.log 2>&1; then - log_ok "Dependencies installed" + log_ok else - log_fail "npm install failed" + log_fail log_detail "$(cat /tmp/npm-install.log)" exit 1 fi fi -log_section "Test" log_action "Running TypeScript test runner" # Run the TypeScript headless test runner with all arguments