commit 0d8ef2904dbd1c9ab617e157e2fc77c681c40005 Author: cneicy Date: Sat Sep 5 03:18:06 2026 +0800 feat: add shared runtime abstractions packages diff --git a/.gitea/workflows/publish-nuget.yml b/.gitea/workflows/publish-nuget.yml new file mode 100644 index 0000000..38be611 --- /dev/null +++ b/.gitea/workflows/publish-nuget.yml @@ -0,0 +1,50 @@ +name: Publish NuGet packages + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + NUGET_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }} + steps: + - name: Checkout tagged source + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Validate tag and pack projects + shell: bash + run: | + set -euo pipefail + tag="${GITEA_REF#refs/tags/}" + version="$(node -p "require('./package.json').version")" + test "$tag" = "v$version" + mkdir -p packages + mapfile -d '' projects < <(find DotNet~ Godot~ -type f -name '*.csproj' -print0 2>/dev/null || true) + test "${#projects[@]}" -gt 0 + for project in "${projects[@]}"; do + dotnet restore "$project" --configfile NuGet.Config + dotnet pack "$project" --configuration Release --no-restore --output "$PWD/packages" --include-symbols --include-source + done + find packages -maxdepth 1 -name '*.nupkg' -type f | grep -q . + + - name: Publish packages + shell: bash + run: | + set -euo pipefail + : "${NUGET_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}" + dotnet nuget push 'packages/*.nupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate + if compgen -G 'packages/*.snupkg' > /dev/null; then + dotnet nuget push 'packages/*.snupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate + fi diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..0d171de --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,67 @@ +name: Publish UPM package + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + NODE_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }} + steps: + - name: Fetch exact tagged release archive + env: + GITEA_REF: ${{ gitea.ref }} + shell: bash + run: | + set -eu + tag="${GITEA_REF#refs/tags/}" + case "$tag" in + v[0-9]*) ;; + *) echo "Expected a version tag ref, got: $GITEA_REF" >&2; exit 1 ;; + esac + export SHRINKSDK_ARCHIVE_URL="https://git.crash.work/ShrinkSDK/ShrinkRuntime.Abstractions/archive/${tag}.tar.gz" + node --input-type=module <<'NODE' + import { writeFile } from 'node:fs/promises'; + + const response = await fetch(process.env.SHRINKSDK_ARCHIVE_URL); + if (!response.ok) { + throw new Error(`Release archive download failed: ${response.status} ${response.statusText}`); + } + + await writeFile('release.tar.gz', new Uint8Array(await response.arrayBuffer())); + NODE + mkdir release + tar -xzf release.tar.gz --strip-components=1 -C release + rm -f release.tar.gz + printf '%s' "$tag" > release/.shrink-sdk-release-tag + + - name: Validate immutable release version + shell: bash + run: | + set -eu + cd release + tag="$(cat .shrink-sdk-release-tag)" + version="$(node -p "require('./package.json').version")" + test "$tag" = "v$version" + npm pack --dry-run + + - name: Publish to ShrinkSDK registry + shell: bash + run: | + set -eu + : "${NODE_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}" + cd release + npmrc="$HOME/.npmrc" + cleanup() { rm -f "$npmrc"; } + trap cleanup EXIT + printf '%s\n' \ + 'registry=https://git.crash.work/api/packages/ShrinkSDK/npm/' \ + '//git.crash.work/api/packages/ShrinkSDK/npm/:_authToken=${NODE_AUTH_TOKEN}' > "$npmrc" + npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b88af1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/bin/ +**/obj/ +artifacts/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..39111c5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +.git/ +.gitea/ +Development~/ +Tools~/ +DotNet~/ +Godot~/ +NuGet.Config +*.csproj +*.sln +*.user +*.DotSettings.user diff --git a/DotNet~/ShrinkSDK.Runtime.Abstractions.csproj b/DotNet~/ShrinkSDK.Runtime.Abstractions.csproj new file mode 100644 index 0000000..eb382d1 --- /dev/null +++ b/DotNet~/ShrinkSDK.Runtime.Abstractions.csproj @@ -0,0 +1,14 @@ + + + netstandard2.1 + false + ShrinkRuntime.Abstractions + ShrinkSDK.Runtime + ShrinkSDK.Runtime.Abstractions + 0.1.0 + Engine-neutral ShrinkSDK runtime contracts. + + + + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..43e134b --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1d2e81 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# ShrinkSDK Runtime Abstractions + +Unity 和普通 .NET/Godot 宿主共用的平台服务合同,包含日志、主线程派发、时钟、持久化路径、截图和应用生命周期。 + +- Unity:安装 `com.cneicy.shrink-runtime-abstractions`。 +- .NET/Godot:安装 `ShrinkSDK.Runtime.Abstractions`。 +- NuGet 打包工程位于 `DotNet~/ShrinkSDK.Runtime.Abstractions.csproj`。 diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..a4cfb82 --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fdcff332146fc1d439fc0c82285637da +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ShrinkRuntime.Abstractions.asmdef b/Runtime/ShrinkRuntime.Abstractions.asmdef new file mode 100644 index 0000000..e191c12 --- /dev/null +++ b/Runtime/ShrinkRuntime.Abstractions.asmdef @@ -0,0 +1,5 @@ +{ + "name": "ShrinkRuntime.Abstractions", + "rootNamespace": "ShrinkSDK.Runtime", + "autoReferenced": true +} diff --git a/Runtime/ShrinkRuntime.Abstractions.asmdef.meta b/Runtime/ShrinkRuntime.Abstractions.asmdef.meta new file mode 100644 index 0000000..8cd5fd9 --- /dev/null +++ b/Runtime/ShrinkRuntime.Abstractions.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9285a23e48a9fd04ea5d3a7507010d2b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ShrinkRuntimeServices.cs b/Runtime/ShrinkRuntimeServices.cs new file mode 100644 index 0000000..afcb260 --- /dev/null +++ b/Runtime/ShrinkRuntimeServices.cs @@ -0,0 +1,127 @@ +#nullable enable + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace ShrinkSDK.Runtime +{ + public enum ShrinkLogLevel { Trace, Debug, Information, Warning, Error } + + public interface IShrinkLogger + { + void Log(ShrinkLogLevel level, string message, Exception? exception = null); + } + + public interface IShrinkMainThreadDispatcher + { + bool IsMainThread { get; } + bool TryPost(Action action); + } + + public interface IShrinkClock + { + DateTimeOffset UtcNow { get; } + double UnscaledTimeSeconds { get; } + } + + public interface IShrinkPathProvider + { + string PersistentDataPath { get; } + string TemporaryDataPath { get; } + } + + public interface IShrinkScreenshotProvider + { + ValueTask CapturePngAsync(CancellationToken cancellationToken = default); + } + + public interface IShrinkApplicationLifecycle + { + event Action? Paused; + event Action? Resumed; + event Action? Exiting; + } + + public static class ShrinkLoggerExtensions + { + public static void Error(this IShrinkLogger logger, string message, Exception? exception = null) => + logger.Log(ShrinkLogLevel.Error, message, exception); + } + + public static class ShrinkRuntimeServices + { + private static IShrinkLogger _logger = new ConsoleLogger(); + private static IShrinkMainThreadDispatcher _dispatcher = new InlineDispatcher(); + private static IShrinkClock _clock = new SystemClock(); + private static IShrinkPathProvider _paths = new SystemPaths(); + + public static IShrinkLogger Logger => _logger; + public static IShrinkMainThreadDispatcher Dispatcher => _dispatcher; + public static IShrinkClock Clock => _clock; + public static IShrinkPathProvider Paths => _paths; + public static IShrinkScreenshotProvider? Screenshots { get; private set; } + public static IShrinkApplicationLifecycle? Lifecycle { get; private set; } + + public static void Configure(IShrinkLogger logger, IShrinkMainThreadDispatcher dispatcher, + IShrinkClock clock, IShrinkPathProvider paths, + IShrinkScreenshotProvider? screenshots = null, IShrinkApplicationLifecycle? lifecycle = null) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + _paths = paths ?? throw new ArgumentNullException(nameof(paths)); + Screenshots = screenshots; + Lifecycle = lifecycle; + } + + private sealed class ConsoleLogger : IShrinkLogger + { + public void Log(ShrinkLogLevel level, string message, Exception? exception = null) + { + Console.WriteLine($"[{level}] {message}"); + if (exception != null) Console.WriteLine(exception); + } + } + + private sealed class InlineDispatcher : IShrinkMainThreadDispatcher + { + public bool IsMainThread => true; + public bool TryPost(Action action) + { + if (action == null) return false; + action(); + return true; + } + } + + private sealed class SystemClock : IShrinkClock + { + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + public double UnscaledTimeSeconds => _stopwatch.Elapsed.TotalSeconds; + } + + private sealed class SystemPaths : IShrinkPathProvider + { + public string PersistentDataPath => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ShrinkSDK"); + public string TemporaryDataPath => Path.GetTempPath(); + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public sealed class ShrinkCodeGenWovenAttribute : Attribute + { + public ShrinkCodeGenWovenAttribute(string weaverVersion, string inputMvid) + { + WeaverVersion = weaverVersion; + InputMvid = inputMvid; + } + + public string WeaverVersion { get; } + public string InputMvid { get; } + } +} diff --git a/Runtime/ShrinkRuntimeServices.cs.meta b/Runtime/ShrinkRuntimeServices.cs.meta new file mode 100644 index 0000000..d660e28 --- /dev/null +++ b/Runtime/ShrinkRuntimeServices.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f9b07df5ab1dc345be31f33e94077d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..50f75d9 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "com.cneicy.shrink-runtime-abstractions", + "version": "0.1.0", + "displayName": "ShrinkSDK Runtime Abstractions", + "description": "Engine-neutral runtime services and CodeGen marker contracts.", + "unity": "2022.3" +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..8d8096e --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2f07481a8e08f454fa1d85c5afe4c92d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: