Compare commits

...
2 Commits
Author SHA1 Message Date
cneicy 88ba3cdc48 feat: add Godot C# compatibility and shared codegen weaving
Validate ShrinkSDK Workspace / unity (push) Failing after 3m18s
2026-09-05 02:37:43 +08:00
cneicy 60cf457230 docs 2026-09-04 23:03:02 +08:00
98 changed files with 2806 additions and 19 deletions
+1
View File
@@ -143,3 +143,4 @@ GeneratedServers/ExampleGreedMod.Rules/*
!GeneratedServers/ExampleGreedMod.Rules/ExampleGreedMod.Rules.csproj
!GeneratedServers/ExampleGreedMod.Rules/pack-mod.ps1
GeneratedModSdk/
.mimosa/
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 10936b7d408f403449df0946277a2d00
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fdcff332146fc1d439fc0c82285637da
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
{
"name": "ShrinkRuntime.Abstractions",
"rootNamespace": "ShrinkSDK.Runtime",
"autoReferenced": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9285a23e48a9fd04ea5d3a7507010d2b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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<byte[]> 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; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f9b07df5ab1dc345be31f33e94077d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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"
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2f07481a8e08f454fa1d85c5afe4c92d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+7
View File
@@ -0,0 +1,7 @@
**/bin/
**/obj/
**/.godot/
Artifacts/
Exports/
!*.csproj
!*.sln
@@ -0,0 +1,9 @@
## Release 0.1.0
### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
SHRINK001 | ShrinkSDK.CodeGen | Error | Invalid event subscriber signature
SHRINK002 | ShrinkSDK.CodeGen | Error | Duplicate network opcode and route
SHRINK003 | ShrinkSDK.CodeGen | Error | Invalid application installer
@@ -0,0 +1,75 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace ShrinkSDK.CodeGen.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ShrinkCodeGenAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor InvalidEventHandler = new(
"SHRINK001", "Invalid event subscriber signature",
"Method '{0}' must use void(TEvent), UniTask(TEvent), or UniTask(TEvent, CancellationToken)",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
private static readonly DiagnosticDescriptor DuplicateNetworkContract = new(
"SHRINK002", "Duplicate network opcode and route",
"Network opcode/route '{0}' is already declared by '{1}'",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
private static readonly DiagnosticDescriptor InvalidInstaller = new(
"SHRINK003", "Invalid application installer",
"Type '{0}' has ShrinkAppModuleInstaller but does not implement IShrinkAppModuleInstaller",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(InvalidEventHandler, DuplicateNetworkContract, InvalidInstaller);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(start =>
{
var networkContracts = new ConcurrentDictionary<string, INamedTypeSymbol>(StringComparer.Ordinal);
start.RegisterSymbolAction(symbolContext => AnalyzeMethod(symbolContext), SymbolKind.Method);
start.RegisterSymbolAction(symbolContext => AnalyzeType(symbolContext, networkContracts), SymbolKind.NamedType);
});
}
private static void AnalyzeMethod(SymbolAnalysisContext context)
{
var method = (IMethodSymbol)context.Symbol;
if (!HasAttribute(method, "ShrinkEventBus.ShrinkSubscribeAttribute")) return;
var parametersValid = method.Parameters.Length == 1 ||
method.Parameters.Length == 2 && method.Parameters[1].Type.ToDisplayString() == "System.Threading.CancellationToken";
var returnName = method.ReturnType.ToDisplayString();
var returnValid = method.Parameters.Length == 1 && (method.ReturnsVoid || returnName == "Cysharp.Threading.Tasks.UniTask") ||
method.Parameters.Length == 2 && returnName == "Cysharp.Threading.Tasks.UniTask";
if (!parametersValid || !returnValid)
context.ReportDiagnostic(Diagnostic.Create(InvalidEventHandler, method.Locations.FirstOrDefault(), method.Name));
}
private static void AnalyzeType(SymbolAnalysisContext context, ConcurrentDictionary<string, INamedTypeSymbol> networkContracts)
{
var type = (INamedTypeSymbol)context.Symbol;
if (HasAttribute(type, "ShrinkApp.ShrinkAppModuleInstallerAttribute") &&
!type.AllInterfaces.Any(item => item.ToDisplayString() == "ShrinkApp.IShrinkAppModuleInstaller"))
context.ReportDiagnostic(Diagnostic.Create(InvalidInstaller, type.Locations.FirstOrDefault(), type.Name));
var network = type.GetAttributes().FirstOrDefault(attribute =>
attribute.AttributeClass?.ToDisplayString() == "ShrinkNetwork.ShrinkNetworkMessageAttribute");
if (network == null || network.ConstructorArguments.Length == 0) return;
var opcode = network.ConstructorArguments[0].Value?.ToString() ?? string.Empty;
var route = network.ConstructorArguments.Length > 1 ? network.ConstructorArguments[1].Value as string ?? string.Empty : string.Empty;
var key = opcode + ":" + route;
if (!networkContracts.TryAdd(key, type) && networkContracts.TryGetValue(key, out var previous))
context.ReportDiagnostic(Diagnostic.Create(DuplicateNetworkContract, type.Locations.FirstOrDefault(), key, previous.Name));
}
private static bool HasAttribute(ISymbol symbol, string fullName) =>
symbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == fullName);
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Analyzers</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen.Analyzers</RootNamespace>
<IsPackable>false</IsPackable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Core</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen</RootNamespace>
<PackageId>ShrinkSDK.CodeGen.Core</PackageId>
<Version>0.1.0</Version>
<IsPackable>true</IsPackable>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkShared.CodeGen\Editor\Core\*.cs" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
</Project>
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Task</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen</RootNamespace>
<PackageId>ShrinkSDK.CodeGen</PackageId>
<Version>0.1.0</Version>
<Description>MSBuild integration for ShrinkSDK Cecil weaving.</Description>
<IsPackable>true</IsPackable>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeShrinkCodeGenDependencies</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ShrinkSDK.CodeGen.Core\ShrinkSDK.CodeGen.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.CodeGen.Analyzers\ShrinkSDK.CodeGen.Analyzers.csproj" ReferenceOutputAssembly="false" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.14.28" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.14.28" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="buildTransitive\ShrinkSDK.CodeGen.props" Pack="true" PackagePath="buildTransitive\ShrinkSDK.CodeGen.props" />
<None Include="buildTransitive\ShrinkSDK.CodeGen.targets" Pack="true" PackagePath="buildTransitive\ShrinkSDK.CodeGen.targets" />
<None Include="..\ShrinkSDK.CodeGen.Analyzers\bin\$(Configuration)\netstandard2.0\ShrinkSDK.CodeGen.Analyzers.dll"
Pack="true" PackagePath="analyzers\dotnet\cs\ShrinkSDK.CodeGen.Analyzers.dll" />
</ItemGroup>
<Target Name="IncludeShrinkCodeGenDependencies" DependsOnTargets="Build">
<ItemGroup>
<BuildOutputInPackage Include="$(OutputPath)ShrinkSDK.CodeGen.Core.dll" />
<BuildOutputInPackage Include="$(OutputPath)Mono.Cecil.dll" />
</ItemGroup>
</Target>
</Project>
@@ -0,0 +1,98 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace ShrinkSDK.CodeGen;
public sealed class ShrinkWeaveTask : Task
{
[Required]
public string AssemblyPath { get; set; } = string.Empty;
public string? PdbPath { get; set; }
public ITaskItem[] ReferencePaths { get; set; } = Array.Empty<ITaskItem>();
public string? StrongNameKeyFile { get; set; }
[Output] public bool Changed { get; private set; }
[Output] public int InstanceSubscriberCount { get; private set; }
[Output] public int StaticSubscriberCount { get; private set; }
[Output] public int RegistryEntryCount { get; private set; }
public override bool Execute()
{
if (!File.Exists(AssemblyPath))
{
Log.LogError($"[ShrinkSDK.CodeGen] Intermediate assembly does not exist: {AssemblyPath}");
return false;
}
var assemblyDirectory = Path.GetDirectoryName(Path.GetFullPath(AssemblyPath))!;
var temporaryAssembly = Path.Combine(assemblyDirectory, $".{Path.GetFileName(AssemblyPath)}.{Guid.NewGuid():N}.tmp");
var hasPdb = !string.IsNullOrWhiteSpace(PdbPath) && File.Exists(PdbPath);
var temporaryPdb = hasPdb
? Path.Combine(assemblyDirectory, $".{Path.GetFileName(PdbPath)}.{Guid.NewGuid():N}.tmp")
: null;
try
{
var result = ShrinkAssemblyWeaver.Weave(
AssemblyPath,
hasPdb ? PdbPath : null,
ReferencePaths.Select(item => item.ItemSpec),
temporaryAssembly,
temporaryPdb,
ShrinkCodeGenPlatform.EngineNeutral,
StrongNameKeyFile);
foreach (var diagnostic in result.Diagnostics)
{
switch (diagnostic.Severity)
{
case ShrinkCodeGenDiagnosticSeverity.Warning:
Log.LogWarning($"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
case ShrinkCodeGenDiagnosticSeverity.Error:
Log.LogError($"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
default:
Log.LogMessage(MessageImportance.Low, $"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
}
}
if (!result.Succeeded)
return false;
Changed = result.Changed;
InstanceSubscriberCount = result.InstanceSubscribers;
StaticSubscriberCount = result.StaticSubscribers;
RegistryEntryCount = result.RegistryEntries;
if (Changed)
{
File.Move(temporaryAssembly, AssemblyPath, true);
if (hasPdb && temporaryPdb != null)
File.Move(temporaryPdb, PdbPath!, true);
}
Log.LogMessage(MessageImportance.High,
$"[ShrinkSDK.CodeGen] {Path.GetFileName(AssemblyPath)} woven: instance={InstanceSubscriberCount}, static={StaticSubscriberCount}, registry={RegistryEntryCount}.");
return true;
}
catch (Exception exception)
{
Log.LogErrorFromException(exception, true);
return false;
}
finally
{
TryDelete(temporaryAssembly);
if (temporaryPdb != null) TryDelete(temporaryPdb);
}
}
private static void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); }
catch { }
}
}
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<ShrinkCodeGenEnabled Condition="'$(ShrinkCodeGenEnabled)' == ''">true</ShrinkCodeGenEnabled>
</PropertyGroup>
</Project>
@@ -0,0 +1,18 @@
<Project>
<PropertyGroup>
<_ShrinkCodeGenTaskAssembly Condition="'$(_ShrinkCodeGenTaskAssembly)' == ''">$(MSBuildThisFileDirectory)..\tools\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<UsingTask TaskName="ShrinkSDK.CodeGen.ShrinkWeaveTask" AssemblyFile="$(_ShrinkCodeGenTaskAssembly)" />
<Target Name="ShrinkCodeGenWeave"
AfterTargets="CoreCompile"
BeforeTargets="CopyFilesToOutputDirectory"
Condition="'$(ShrinkCodeGenEnabled)' == 'true' and '$(DesignTimeBuild)' != 'true'">
<PropertyGroup>
<_ShrinkCodeGenIntermediateAssembly>$(IntermediateOutputPath)$(TargetName).dll</_ShrinkCodeGenIntermediateAssembly>
</PropertyGroup>
<ShrinkWeaveTask AssemblyPath="$(_ShrinkCodeGenIntermediateAssembly)"
PdbPath="$(IntermediateOutputPath)$(TargetName).pdb"
ReferencePaths="@(ReferencePath)"
StrongNameKeyFile="$(ShrinkCodeGenStrongNameKeyFile)" />
</Target>
</Project>
+13
View File
@@ -0,0 +1,13 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<RepositoryUrl>https://git.crash.work/ShrinkSDK/Workspace</RepositoryUrl>
<Authors>ShrinkSDK</Authors>
<Company>ShrinkSDK</Company>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
</Project>
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkSDK.Godot.Installer</AssemblyName>
<PackageId>ShrinkSDK.Godot.Installer</PackageId>
<Version>0.1.1</Version>
<Description>Godot Editor package manager and CodeGen diagnostics for ShrinkSDK.</Description>
</PropertyGroup>
<ItemGroup>
<Compile Include="addons\shrinksdk\*.cs" />
<Compile Include="..\..\Assets\Modules\ShrinkInstaller\Editor\ShrinkPackageVersion.cs" Link="addons\shrinksdk\ShrinkPackageVersion.cs" />
<PackageReference Include="GodotSharp" Version="4.6.3" />
<PackageReference Include="GodotSharpEditor" Version="4.6.3" />
<None Include="addons\shrinksdk\plugin.cfg" Pack="true" PackagePath="contentFiles\any\any\addons\shrinksdk\plugin.cfg" />
<None Include="addons\shrinksdk\*.cs" Pack="true" PackagePath="contentFiles\any\any\addons\shrinksdk\" />
<None Include="..\..\Assets\Modules\ShrinkInstaller\Editor\ShrinkPackageVersion.cs" Pack="true" PackagePath="contentFiles\any\any\addons\shrinksdk\ShrinkPackageVersion.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,133 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Godot;
using ShrinkSDK.Installer;
namespace ShrinkSDK.Godot.Editor;
[Tool]
public partial class ShrinkGodotInstallerPlugin : EditorPlugin
{
private EditorDock? _panel;
private Label? _status;
private RichTextLabel? _output;
private readonly System.Collections.Generic.Dictionary<string, Button> _installActions =
new(StringComparer.OrdinalIgnoreCase);
private readonly System.Collections.Generic.Dictionary<string, Button> _removeActions =
new(StringComparer.OrdinalIgnoreCase);
private string ProjectDirectory => ProjectSettings.GlobalizePath("res://");
private string ProjectPath => Directory.GetFiles(ProjectDirectory, "*.csproj", SearchOption.TopDirectoryOnly).Single();
public override void _EnterTree()
{
_panel = new EditorDock { Name = "ShrinkSDK", Title = "ShrinkSDK", DefaultSlot = EditorDock.DockSlot.LeftBr };
var content = new VBoxContainer();
_panel.AddChild(content);
content.AddChild(new Label { Text = "ShrinkSDK Packages" });
_installActions.Clear();
_removeActions.Clear();
foreach (var package in ShrinkProjectPackageEditor.RecommendedPackages)
{
var row = new HBoxContainer();
row.AddChild(new Label { Text = $"{package.Key} {package.Value}", SizeFlagsHorizontal = Control.SizeFlags.ExpandFill });
var install = new Button { Text = "Install / Update" };
install.Pressed += () => ChangePackage(package.Key, package.Value);
row.AddChild(install);
_installActions[package.Key] = install;
var remove = new Button { Text = "Remove" };
remove.Pressed += () => ChangePackage(package.Key, null);
row.AddChild(remove);
_removeActions[package.Key] = remove;
content.AddChild(row);
}
var restore = new Button { Text = "Restore and Build" };
restore.Pressed += BuildProject;
content.AddChild(restore);
_status = new Label { Text = "CodeGen: not built" };
content.AddChild(_status);
_output = new RichTextLabel { FitContent = true, CustomMinimumSize = new Vector2(360, 180) };
content.AddChild(_output);
AddDock(_panel);
RefreshInstalledState();
}
public override void _ExitTree()
{
if (_panel == null) return;
RemoveDock(_panel);
_panel.QueueFree();
_panel = null;
}
private async void ChangePackage(string packageId, string? version)
{
try
{
ShrinkProjectPackageEditor.EnsureShrinkFeed(Path.Combine(ProjectDirectory, "NuGet.Config"));
ShrinkProjectPackageEditor.SetPackageReference(ProjectPath, packageId, version);
await RunBuild("restore");
}
catch (Exception exception) { ShowFailure(exception); }
}
private async void BuildProject()
{
try { await RunBuild("build"); }
catch (Exception exception) { ShowFailure(exception); }
}
private async System.Threading.Tasks.Task RunBuild(string command)
{
_status!.Text = $"CodeGen: running dotnet {command}";
var result = await ShrinkProjectPackageEditor.RunDotnetAsync(ProjectDirectory, $"{command} \"{ProjectPath}\" /nr:false");
_output!.Text = result.Output;
var match = Regex.Matches(result.Output, @"\[ShrinkSDK\.CodeGen\].*woven: instance=(\d+), static=(\d+), registry=(\d+)").Cast<Match>().LastOrDefault();
_status.Text = result.ExitCode == 0
? match == null ? "CodeGen: build succeeded (no attributed registrations)" : $"CodeGen: woven; instance={match.Groups[1].Value}, static={match.Groups[2].Value}, registry={match.Groups[3].Value}"
: "CodeGen: build failed";
RefreshInstalledState();
}
private void RefreshInstalledState()
{
if (_output == null) return;
var installed = File.Exists(ProjectPath)
? ShrinkProjectPackageEditor.ReadPackageReferences(ProjectPath)
: new System.Collections.Generic.Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var package in ShrinkProjectPackageEditor.RecommendedPackages)
{
var hasInstalled = installed.TryGetValue(package.Key, out var installedVersion);
var relation = hasInstalled
? ShrinkPackageVersion.Classify(installedVersion, package.Value)
: ShrinkPackageVersionRelation.Unknown;
if (_installActions.TryGetValue(package.Key, out var install))
{
install.Text = !hasInstalled
? $"Install {package.Value}"
: relation switch
{
ShrinkPackageVersionRelation.Equal => $"Installed {installedVersion}",
ShrinkPackageVersionRelation.Newer => $"Newer {installedVersion}",
ShrinkPackageVersionRelation.Older => $"Upgrade {installedVersion} → {package.Value}",
_ => $"Update {installedVersion} → {package.Value}"
};
install.Disabled = hasInstalled && relation is
ShrinkPackageVersionRelation.Equal or ShrinkPackageVersionRelation.Newer;
}
if (_removeActions.TryGetValue(package.Key, out var remove))
remove.Disabled = !hasInstalled;
}
_output.Text = "Direct packages: " + string.Join(", ", installed.Select(item => $"{item.Key}@{item.Value}"));
}
private void ShowFailure(Exception exception)
{
_status!.Text = "CodeGen: operation failed";
_output!.Text = exception.ToString();
GD.PushError(exception.ToString());
}
}
@@ -0,0 +1,148 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace ShrinkSDK.Godot.Editor;
public static class ShrinkProjectPackageEditor
{
public static readonly IReadOnlyDictionary<string, string> RecommendedPackages =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["ShrinkSDK.Godot"] = "0.1.0",
["ShrinkSDK.EventBus"] = "2.1.0",
["ShrinkSDK.Context.Core"] = "0.2.0",
["ShrinkSDK.Command"] = "0.3.0",
["ShrinkSDK.Command.Integration.EventBus"] = "0.2.0",
["ShrinkSDK.Command.Integration.Network"] = "0.2.0",
["ShrinkSDK.Command.Integration.App"] = "0.2.0",
["ShrinkSDK.Network"] = "0.3.0",
["ShrinkSDK.Network.Integration.EventBus"] = "0.2.0",
["ShrinkSDK.Network.Integration.App"] = "0.2.0",
["ShrinkSDK.DataSaver"] = "2.3.0",
["ShrinkSDK.DataSaver.Integration.EventBus"] = "2.2.0",
["ShrinkSDK.DataSaver.Integration.App"] = "0.2.0",
["ShrinkSDK.App.Core"] = "0.2.0",
["ShrinkSDK.Context.AppAdapter"] = "0.2.0",
["ShrinkSDK.App.Starter.Basic"] = "0.3.0",
["ShrinkSDK.ModFramework"] = "0.3.0",
["ShrinkSDK.ModFramework.Godot"] = "0.1.0",
["ShrinkSDK.Tutorial"] = "0.2.0",
["ShrinkSDK.Tutorial.Godot"] = "0.1.0"
};
public static IReadOnlyDictionary<string, string> ReadPackageReferences(string projectPath)
{
var document = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace);
return document.Descendants("PackageReference")
.Select(element => new
{
Id = ((string?)element.Attribute("Include") ?? (string?)element.Attribute("Update"))?.Trim(),
Version = ((string?)element.Attribute("Version") ??
(string?)element.Attribute("VersionOverride") ??
element.Element("Version")?.Value)?.Trim() ?? string.Empty
})
.Where(item => !string.IsNullOrWhiteSpace(item.Id))
.GroupBy(item => item.Id!, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.First().Version, StringComparer.OrdinalIgnoreCase);
}
public static void SetPackageReference(string projectPath, string packageId, string? version)
{
var document = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace);
var root = document.Root ?? throw new InvalidDataException("The C# project has no root element.");
var references = root.Descendants("PackageReference").Where(element =>
string.Equals((string?)element.Attribute("Include"), packageId, StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (var duplicate in references.Skip(1)) duplicate.Remove();
if (string.IsNullOrWhiteSpace(version))
{
foreach (var reference in references) reference.Remove();
}
else if (references.Length > 0)
{
var reference = references[0];
if (reference.Attribute("VersionOverride") != null)
reference.SetAttributeValue("VersionOverride", version);
else if (reference.Attribute("Version") != null)
reference.SetAttributeValue("Version", version);
else if (reference.Element("Version") != null)
reference.Element("Version")!.Value = version;
else
reference.SetAttributeValue("Version", version);
}
else
{
var group = root.Elements("ItemGroup").FirstOrDefault(element => element.Elements("PackageReference").Any());
if (group == null)
{
group = new XElement("ItemGroup");
root.Add(group);
}
group.Add(new XElement("PackageReference", new XAttribute("Include", packageId), new XAttribute("Version", version)));
}
WriteAtomically(projectPath, document);
}
public static void EnsureShrinkFeed(string nugetConfigPath)
{
XDocument document;
if (File.Exists(nugetConfigPath)) document = XDocument.Load(nugetConfigPath, LoadOptions.PreserveWhitespace);
else document = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("configuration"));
var root = document.Root ?? throw new InvalidDataException("NuGet.Config has no root element.");
var sources = root.Element("packageSources");
if (sources == null)
{
sources = new XElement("packageSources");
root.Add(sources);
}
var existing = sources.Elements("add").FirstOrDefault(element =>
string.Equals((string?)element.Attribute("key"), "ShrinkSDK", StringComparison.OrdinalIgnoreCase));
if (existing == null)
sources.Add(new XElement("add", new XAttribute("key", "ShrinkSDK"),
new XAttribute("value", "https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json")));
else
existing.SetAttributeValue("value", "https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json");
WriteAtomically(nugetConfigPath, document);
}
public static async Task<(int ExitCode, string Output)> RunDotnetAsync(string projectDirectory,
string arguments, CancellationToken cancellationToken = default)
{
var output = new StringBuilder();
using var process = new Process
{
StartInfo = new ProcessStartInfo("dotnet", arguments)
{
WorkingDirectory = projectDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.OutputDataReceived += (_, args) => { if (args.Data != null) output.AppendLine(args.Data); };
process.ErrorDataReceived += (_, args) => { if (args.Data != null) output.AppendLine(args.Data); };
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync(cancellationToken);
return (process.ExitCode, output.ToString());
}
private static void WriteAtomically(string path, XDocument document)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!);
var temporaryPath = path + ".shrinksdk.tmp";
using (var writer = new StreamWriter(temporaryPath, false, new UTF8Encoding(false)))
document.Save(writer, SaveOptions.DisableFormatting);
File.Move(temporaryPath, path, true);
}
}
@@ -0,0 +1,7 @@
[plugin]
name="ShrinkSDK Installer"
description="Installs and diagnoses ShrinkSDK NuGet modules and CodeGen weaving."
author="ShrinkSDK"
version="0.1.1"
script="ShrinkGodotInstallerPlugin.cs"
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="Godot Local" value="C:\Users\im\Documents\godothub\engine\4.6.3-stable-mono\GodotSharp\Tools\nupkgs" />
<add key="ShrinkSDK Local" value="Artifacts" />
<add key="ShrinkSDK" value="https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
@@ -0,0 +1,137 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
namespace ShrinkApp;
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ShrinkAppModuleInstallerAttribute : Attribute { }
public interface IShrinkAppModuleInstaller
{
string ModuleId { get; }
int Order { get; }
IReadOnlyList<string> DependsOn { get; }
void RegisterServices(ShrinkAppContext context);
UniTask InitializeAsync(ShrinkAppContext context);
}
public interface IShrinkAppModuleShutdown
{
UniTask ShutdownAsync(ShrinkAppContext context);
}
public sealed class ShrinkAppSettings
{
public bool VerboseLogging { get; set; }
public string[] DisabledModuleIds { get; set; } = Array.Empty<string>();
}
public sealed class ShrinkAppServices
{
private readonly Dictionary<Type, object> _services = new();
public void Register<T>(T service) where T : class => _services[typeof(T)] = service ?? throw new ArgumentNullException(nameof(service));
public bool TryGet<T>(out T? service) where T : class
{
service = _services.TryGetValue(typeof(T), out var value) ? value as T : null;
return service != null;
}
public T GetRequired<T>() where T : class => TryGet<T>(out var value) ? value! : throw new InvalidOperationException($"Required service is not registered: {typeof(T).FullName}");
public bool TryUnregister<T>(T service) where T : class => _services.TryGetValue(typeof(T), out var value) && ReferenceEquals(value, service) && _services.Remove(typeof(T));
}
public sealed class ShrinkAppContext
{
public ShrinkAppContext(object? host, ShrinkAppSettings settings, ShrinkAppServices services)
{
Host = host;
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
Services = services ?? throw new ArgumentNullException(nameof(services));
}
public object? Host { get; }
public ShrinkAppSettings Settings { get; }
public ShrinkAppServices Services { get; }
public static ShrinkAppContext CreateStandalone(ShrinkAppServices services, ShrinkAppSettings? settings = null) => new(null, settings ?? new ShrinkAppSettings(), services);
}
public sealed class ShrinkAppRuntime
{
private readonly List<IShrinkAppModuleInstaller> _started = new();
public ShrinkAppRuntime(object? host = null, ShrinkAppSettings? settings = null, ShrinkAppServices? services = null)
{
Context = new ShrinkAppContext(host, settings ?? new ShrinkAppSettings(), services ?? new ShrinkAppServices());
}
public ShrinkAppContext Context { get; }
public bool IsRunning { get; private set; }
public IReadOnlyList<string> StartedModules => _started.Select(item => item.ModuleId).ToArray();
public async UniTask StartAsync()
{
if (IsRunning) return;
var installers = Sort(CreateInstallers(), Context.Settings.DisabledModuleIds);
try
{
foreach (var installer in installers) installer.RegisterServices(Context);
foreach (var installer in installers)
{
await installer.InitializeAsync(Context);
_started.Add(installer);
}
IsRunning = true;
}
catch
{
await ShutdownStartedAsync();
throw;
}
}
public async UniTask ShutdownAsync()
{
await ShutdownStartedAsync();
IsRunning = false;
}
private async UniTask ShutdownStartedAsync()
{
for (var index = _started.Count - 1; index >= 0; index--)
if (_started[index] is IShrinkAppModuleShutdown shutdown) await shutdown.ShutdownAsync(Context);
_started.Clear();
}
private static IReadOnlyList<IShrinkAppModuleInstaller> CreateInstallers() =>
ShrinkAppInstallers.GetDiscoveredInstallerTypes().Select(type =>
Activator.CreateInstance(type) as IShrinkAppModuleInstaller ?? throw new InvalidOperationException($"Failed to create installer: {type.FullName}"))
.ToArray();
private static IReadOnlyList<IShrinkAppModuleInstaller> Sort(IEnumerable<IShrinkAppModuleInstaller> source, IEnumerable<string> disabledIds)
{
var disabled = new HashSet<string>(disabledIds ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
var byId = source.Where(item => !disabled.Contains(item.ModuleId)).ToDictionary(item => item.ModuleId, StringComparer.OrdinalIgnoreCase);
var states = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var result = new List<IShrinkAppModuleInstaller>();
foreach (var installer in byId.Values.OrderBy(item => item.Order).ThenBy(item => item.ModuleId, StringComparer.OrdinalIgnoreCase))
Visit(installer, byId, states, result);
return result;
}
private static void Visit(IShrinkAppModuleInstaller installer, IReadOnlyDictionary<string, IShrinkAppModuleInstaller> byId,
IDictionary<string, int> states, ICollection<IShrinkAppModuleInstaller> result)
{
var state = states.TryGetValue(installer.ModuleId, out var value) ? value : 0;
if (state == 2) return;
if (state == 1) throw new InvalidOperationException($"Circular installer dependency: {installer.ModuleId}");
states[installer.ModuleId] = 1;
foreach (var dependencyId in installer.DependsOn ?? Array.Empty<string>())
{
if (!byId.TryGetValue(dependencyId, out var dependency))
throw new InvalidOperationException($"Installer dependency missing. Module={installer.ModuleId}, DependsOn={dependencyId}");
Visit(dependency, byId, states, result);
}
states[installer.ModuleId] = 2;
result.Add(installer);
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkApp.Core.Runtime</AssemblyName>
<RootNamespace>ShrinkApp</RootNamespace>
<PackageId>ShrinkSDK.App.Core</PackageId>
<Version>0.2.0</Version>
<Description>ShrinkSDK engine-neutral application composition runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\ShrinkAppCore.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkApp.Core\Runtime\ShrinkAppGeneratedRegistry.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkApp.Core\Runtime\ShrinkAppInstallers.cs" />
<PackageReference Include="UniTask" Version="2.5.10" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,34 @@
#nullable enable
using System.Collections.Generic;
using ShrinkApp;
using ShrinkCommand.Integration.App;
using ShrinkContext;
using ShrinkContext.AppAdapter;
using ShrinkDataSaver.Integration.App;
using ShrinkNetwork.Integration.App;
namespace ShrinkApp.Starter.Basic;
public static class ShrinkBasicComposition
{
public static ShrinkContextAppHost CreateHost(ShrinkAppServices? services = null)
{
var host = new ShrinkContextAppHost(services);
host.Register<ShrinkCommandAppComponent>("shrink.command");
host.Register<ShrinkDataSaverAppComponent>("shrink.datasaver");
host.Register<ShrinkNetworkAppComponent>("shrink.network");
return host;
}
public static IReadOnlyList<ShrinkLoaderEntry> CreateEntries(ShrinkAppServices services,
ShrinkNetworkAppModuleConfig? network = null) => new[]
{
new ShrinkLoaderEntry("datasaver", "shrink.datasaver", services),
new ShrinkLoaderEntry("command", "shrink.command", services),
new ShrinkLoaderEntry("network", "shrink.network", network ?? new ShrinkNetworkAppModuleConfig
{
Services = services
})
};
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkApp.Starter.Basic</AssemblyName>
<RootNamespace>ShrinkApp.Starter.Basic</RootNamespace>
<PackageId>ShrinkSDK.App.Starter.Basic</PackageId>
<Version>0.3.0</Version>
<Description>Engine-neutral basic ShrinkApp composition.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\*.cs" />
<ProjectReference Include="..\ShrinkSDK.App.Core\ShrinkSDK.App.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.AppAdapter\ShrinkSDK.Context.AppAdapter.csproj" />
<ProjectReference Include="..\ShrinkSDK.Command.Integration.App\ShrinkSDK.Command.Integration.App.csproj" />
<ProjectReference Include="..\ShrinkSDK.DataSaver.Integration.App\ShrinkSDK.DataSaver.Integration.App.csproj" />
<ProjectReference Include="..\ShrinkSDK.Network.Integration.App\ShrinkSDK.Network.Integration.App.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkCommand.Integration.EventBus</AssemblyName>
<RootNamespace>ShrinkCommand.Integration</RootNamespace>
<PackageId>ShrinkSDK.Command.Integration.EventBus</PackageId>
<Version>0.2.0</Version>
<Description>ShrinkCommand and EventBus integration.</Description>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkCommand.Integration.EventBus\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkCommand.Integration.Network</AssemblyName>
<RootNamespace>ShrinkCommand.Integration</RootNamespace>
<PackageId>ShrinkSDK.Command.Integration.Network</PackageId>
<Version>0.2.0</Version>
<Description>ShrinkCommand and Network RPC integration.</Description>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkCommand.Integration.Network\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
<ProjectReference Include="..\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
</ItemGroup>
</Project>
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkCommand.Runtime</AssemblyName>
<RootNamespace>ShrinkCommand</RootNamespace>
<PackageId>ShrinkSDK.Command</PackageId>
<Version>0.3.0</Version>
<Description>ShrinkSDK command parsing, permissions and execution runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkCommand\Runtime\**\*.cs" />
<PackageReference Include="UniTask" Version="2.5.10" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
</ItemGroup>
</Project>
@@ -0,0 +1,31 @@
#nullable enable
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using ShrinkApp;
namespace ShrinkContext.AppAdapter;
public sealed class ShrinkContextAppHost
{
private readonly ShrinkContextRuntime _runtime = new();
private readonly ShrinkComponentCatalog _catalog = new();
private readonly ShrinkContextLoader _loader;
public ShrinkContextAppHost(ShrinkAppServices? services = null)
{
Services = services ?? new ShrinkAppServices();
_loader = new ShrinkContextLoader(_runtime, _catalog);
}
public ShrinkAppServices Services { get; }
public ShrinkContextRuntime Runtime => _runtime;
public ShrinkLoaderTransactionDiagnostic? LastTransaction => _loader.LastTransaction;
public void Register(string name, Func<IShrinkComponent> factory) => _catalog.Register(name, factory);
public void Register<TComponent>(string name) where TComponent : IShrinkComponent, new() =>
_catalog.Register<TComponent>(name);
public UniTask ApplyAsync(IReadOnlyList<ShrinkLoaderEntry> entries) => _loader.ApplyAsync(entries);
public UniTask ShutdownAsync() => _runtime.ShutdownAsync();
}
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkContext.AppAdapter</AssemblyName>
<RootNamespace>ShrinkContext.AppAdapter</RootNamespace>
<PackageId>ShrinkSDK.Context.AppAdapter</PackageId>
<Version>0.2.0</Version>
<Description>Engine-neutral ShrinkContext composition host for ShrinkApp.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.App.Core\ShrinkSDK.App.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkContext.Core.Runtime</AssemblyName>
<RootNamespace>ShrinkContext</RootNamespace>
<PackageId>ShrinkSDK.Context.Core</PackageId>
<Version>0.2.0</Version>
<Description>ShrinkSDK reversible context and fiber runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkContext.Core\Runtime\**\*.cs" />
<PackageReference Include="UniTask" Version="2.5.10" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkDataSaver.Integration.EventBus</AssemblyName>
<RootNamespace>ShrinkDataSaver.Integration</RootNamespace>
<PackageId>ShrinkSDK.DataSaver.Integration.EventBus</PackageId>
<Version>2.2.0</Version>
<Description>ShrinkDataSaver and EventBus integration.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver.Integration.EventBus\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\ShrinkSDK.DataSaver\ShrinkSDK.DataSaver.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,43 @@
#nullable enable
using System.IO;
using Cysharp.Threading.Tasks;
using ShrinkSDK.Runtime;
namespace ShrinkDataSaver;
public sealed class ShrinkDataSaverRuntimeConfig
{
public string? RootPath { get; set; }
public string SaveFileExtension { get; set; } = ".sav";
public string SettingsFileName { get; set; } = "settings.json";
public int CurrentSaveVersion { get; set; } = 1;
public int MaxSlots { get; set; } = 3;
public float SettingsWriteDebounceSeconds { get; set; } = 0.35f;
public bool DontDestroyOnLoadDriver { get; set; }
}
public static class ShrinkDataSaverRuntime
{
public static bool IsInitialized { get; private set; }
public static void Initialize(ShrinkDataSaverRuntimeConfig? config = null)
{
if (IsInitialized) return;
config ??= new ShrinkDataSaverRuntimeConfig();
var root = string.IsNullOrWhiteSpace(config.RootPath)
? ShrinkRuntimeServices.Paths.PersistentDataPath
: config.RootPath!;
Directory.CreateDirectory(root);
var storage = new LocalStorageProvider(root);
ShrinkDataSaverPlatform.SettingsWriteDebounceSeconds = config.SettingsWriteDebounceSeconds;
ShrinkDataSaverPlatform.MaxSlots = config.MaxSlots;
ShrinkSettings.Initialize(storage, Path.Combine(root, config.SettingsFileName));
ShrinkSave.Initialize(storage, Path.Combine(root, "saves"), config.SaveFileExtension,
config.CurrentSaveVersion);
ShrinkSettings.LoadAsync().Forget();
IsInitialized = true;
ShrinkRuntimeServices.Logger.Log(ShrinkLogLevel.Information,
$"[ShrinkDataSaver] Runtime initialized. Root: {root}");
}
}
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkDataSaver.Runtime</AssemblyName>
<RootNamespace>ShrinkDataSaver</RootNamespace>
<PackageId>ShrinkSDK.DataSaver</PackageId>
<Version>2.3.0</Version>
<Description>ShrinkSDK engine-neutral versioned save and settings runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\ShrinkDataSaverRuntime.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\AssemblyInfo.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\DataSerializer.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\IStorageProvider.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\LocalStorageProvider.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\MigrationChain.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\SaveEncryptor.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\SaveTypes.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\ShrinkDataSaverPlatform.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\ShrinkSave.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkDataSaver\Runtime\ShrinkSettings.cs" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="UniTask" Version="2.5.10" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkEventBus.Runtime</AssemblyName>
<RootNamespace>ShrinkEventBus</RootNamespace>
<PackageId>ShrinkSDK.EventBus</PackageId>
<Version>2.1.0</Version>
<Description>ShrinkSDK typed event bus runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkEventBus\Runtime\**\*.cs"
Exclude="..\..\..\Assets\Modules\ShrinkEventBus\Runtime\ShrinkMonoEventScope.cs" />
<PackageReference Include="UniTask" Version="2.5.10" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,154 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Godot;
using ShrinkEventBus;
using ShrinkDataSaver;
using ShrinkApp;
using ShrinkNetwork;
using ShrinkSDK.Runtime;
namespace ShrinkSDK.Godot;
public partial class ShrinkGodotHost : Node, IShrinkMainThreadDispatcher,
IShrinkLogger, IShrinkClock, IShrinkPathProvider, IShrinkScreenshotProvider,
IShrinkApplicationLifecycle
{
private readonly ConcurrentQueue<Action> _dispatchQueue = new();
private readonly Dictionary<ShrinkNetworkService, ShrinkNetworkDispatchQueue> _networkServices = new();
private int _mainThreadId;
private bool _networkPumpRunning;
private ShrinkAppRuntime? _appRuntime;
public event Action? Paused;
public event Action? Resumed;
public event Action? Exiting;
[Export] public int DispatchBudgetPerFrame { get; set; } = 256;
[Export] public int NetworkDispatchBudgetPerFrame { get; set; } = 256;
public bool IsMainThread => Thread.CurrentThread.ManagedThreadId == _mainThreadId;
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
public double UnscaledTimeSeconds => Time.GetTicksMsec() / 1000d;
public string PersistentDataPath => ProjectSettings.GlobalizePath("user://");
public string TemporaryDataPath => OS.GetCacheDir();
public override void _Ready()
{
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
ShrinkRuntimeServices.Configure(this, this, this, this, this, this);
ShrinkEventBusRuntime.ConfigureMainThreadDispatcher(this);
ShrinkDataSaverPlatform.Configure(this, this, this, this);
SetProcess(true);
}
public override void _Process(double delta)
{
var budget = Math.Max(1, DispatchBudgetPerFrame);
while (budget-- > 0 && _dispatchQueue.TryDequeue(out var action))
action();
PumpNetworkAsync().Forget();
}
public override void _Notification(int what)
{
switch ((long)what)
{
case NotificationApplicationPaused:
case NotificationApplicationFocusOut:
Paused?.Invoke();
break;
case NotificationApplicationResumed:
case NotificationApplicationFocusIn:
Resumed?.Invoke();
break;
case NotificationWMCloseRequest:
case NotificationPredelete:
Exiting?.Invoke();
break;
}
}
public async UniTask<ShrinkAppRuntime> StartAppAsync(ShrinkAppSettings? settings = null, ShrinkAppServices? services = null)
{
if (_appRuntime?.IsRunning == true) return _appRuntime;
_appRuntime = new ShrinkAppRuntime(this, settings, services);
await _appRuntime.StartAsync();
return _appRuntime;
}
public async UniTask ShutdownAppAsync()
{
if (_appRuntime == null) return;
await _appRuntime.ShutdownAsync();
_appRuntime = null;
}
public bool TryPost(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
_dispatchQueue.Enqueue(action);
return true;
}
public void AddNetworkService(ShrinkNetworkService service)
{
if (service == null) throw new ArgumentNullException(nameof(service));
if (_networkServices.ContainsKey(service)) return;
var queue = new ShrinkNetworkDispatchQueue(Math.Max(1, NetworkDispatchBudgetPerFrame * 4));
service.DispatchScheduler = queue;
_networkServices.Add(service, queue);
}
public bool RemoveNetworkService(ShrinkNetworkService service)
{
if (!_networkServices.Remove(service, out var queue)) return false;
service.DispatchScheduler = ShrinkNetworkDispatchSchedulers.Inline;
queue.Dispose();
return true;
}
public static ShrinkCodeGenWovenAttribute RequireWoven(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
return assembly.GetCustomAttribute<ShrinkCodeGenWovenAttribute>() ??
throw new InvalidOperationException(
$"Assembly '{assembly.GetName().Name}' was not woven by ShrinkSDK.CodeGen. Ensure the package is installed and ShrinkCodeGenEnabled is not false.");
}
public void Log(ShrinkLogLevel level, string message, Exception? exception = null)
{
var text = exception == null ? message : $"{message}{System.Environment.NewLine}{exception}";
if (level >= ShrinkLogLevel.Error) GD.PushError(text);
else if (level >= ShrinkLogLevel.Warning) GD.PushWarning(text);
else GD.Print(text);
}
public ValueTask<byte[]> CapturePngAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var image = GetViewport().GetTexture().GetImage();
return ValueTask.FromResult(image.SavePngToBuffer());
}
private async UniTaskVoid PumpNetworkAsync()
{
if (_networkPumpRunning) return;
_networkPumpRunning = true;
try
{
foreach (var queue in _networkServices.Values)
await queue.PumpAsync(Math.Max(1, NetworkDispatchBudgetPerFrame));
}
finally
{
_networkPumpRunning = false;
}
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.Godot</AssemblyName>
<RootNamespace>ShrinkSDK.Godot</RootNamespace>
<PackageId>ShrinkSDK.Godot</PackageId>
<Version>0.1.0</Version>
<Description>Godot 4.6 host and platform services for ShrinkSDK.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.6.3" />
<PackageReference Include="UniTask" Version="2.5.10" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<ProjectReference Include="..\ShrinkSDK.DataSaver\ShrinkSDK.DataSaver.csproj" />
<ProjectReference Include="..\ShrinkSDK.App.Core\ShrinkSDK.App.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,66 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace ShrinkModFramework.Godot;
public sealed class ShrinkGodotModLoader : IDisposable
{
private sealed record LoadedMod(AssemblyLoadContext LoadContext, WeakReference UnloadReference,
ShrinkModContext Context, IReadOnlyList<IShrinkMod> Instances);
private readonly Dictionary<string, LoadedMod> _loaded = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlyCollection<string> LoadedAssemblyPaths => _loaded.Keys;
public IReadOnlyList<IShrinkMod> Load(string assemblyPath)
{
assemblyPath = Path.GetFullPath(assemblyPath);
if (_loaded.ContainsKey(assemblyPath)) throw new InvalidOperationException($"Mod assembly is already loaded: {assemblyPath}");
var loadContext = new AssemblyLoadContext($"ShrinkMod:{Path.GetFileNameWithoutExtension(assemblyPath)}", true);
loadContext.Resolving += (_, name) => ResolveDependency(loadContext, Path.GetDirectoryName(assemblyPath)!, name);
try
{
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
var entries = assembly.GetTypes().Where(type => !type.IsAbstract && typeof(IShrinkMod).IsAssignableFrom(type) &&
type.GetCustomAttribute<ShrinkModAttribute>() != null).OrderBy(type => type.GetCustomAttribute<ShrinkModAttribute>()!.LoadOrder).ToArray();
var context = new ShrinkModContext(Path.GetDirectoryName(assemblyPath)!);
var instances = entries.Select(type => (IShrinkMod)(Activator.CreateInstance(type) ??
throw new InvalidOperationException($"Failed to create mod entry: {type.FullName}"))).ToArray();
foreach (var mod in instances) mod.OnConstruct(context);
foreach (var mod in instances) mod.OnRegisterContent(context);
foreach (var mod in instances) mod.OnInitialize(context);
foreach (var mod in instances) mod.OnReady(context);
_loaded[assemblyPath] = new LoadedMod(loadContext, new WeakReference(loadContext), context, instances);
return instances;
}
catch { loadContext.Unload(); throw; }
}
public bool Unload(string assemblyPath)
{
assemblyPath = Path.GetFullPath(assemblyPath);
if (!_loaded.Remove(assemblyPath, out var loaded)) return false;
for (var index = loaded.Instances.Count - 1; index >= 0; index--)
if (loaded.Instances[index] is IShrinkModUnload unload) unload.OnUnload(loaded.Context);
loaded.LoadContext.Unload();
return true;
}
public void Dispose()
{
foreach (var path in _loaded.Keys.ToArray()) Unload(path);
}
private static Assembly? ResolveDependency(AssemblyLoadContext context, string directory, AssemblyName name)
{
var shared = AssemblyLoadContext.Default.Assemblies.FirstOrDefault(assembly => assembly.GetName().Name == name.Name);
if (shared != null) return shared;
var path = Path.Combine(directory, name.Name + ".dll");
return File.Exists(path) ? context.LoadFromAssemblyPath(path) : null;
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkModFramework.Godot</AssemblyName>
<RootNamespace>ShrinkModFramework.Godot</RootNamespace>
<PackageId>ShrinkSDK.ModFramework.Godot</PackageId>
<Version>0.1.0</Version>
<Description>Collectible AssemblyLoadContext host for ShrinkSDK mods in Godot.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ShrinkSDK.ModFramework\ShrinkSDK.ModFramework.csproj" />
<ProjectReference Include="..\ShrinkSDK.Godot\ShrinkSDK.Godot.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,25 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace ShrinkModFramework;
public sealed class ShrinkModContext
{
private readonly Dictionary<Type, object> _services = new();
public ShrinkModContext(string modDirectory) => ModDirectory = modDirectory ?? throw new ArgumentNullException(nameof(modDirectory));
public string ModDirectory { get; }
public void RegisterService<T>(T service) where T : class => _services[typeof(T)] = service ?? throw new ArgumentNullException(nameof(service));
public bool TryGetService<T>(out T? service) where T : class
{
service = _services.TryGetValue(typeof(T), out var value) ? value as T : null;
return service != null;
}
public T GetRequiredService<T>() where T : class => TryGetService<T>(out var value) ? value! : throw new InvalidOperationException($"Service is not registered: {typeof(T).FullName}");
}
public interface IShrinkModUnload
{
void OnUnload(ShrinkModContext context);
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkModFramework.Runtime</AssemblyName>
<RootNamespace>ShrinkModFramework</RootNamespace>
<PackageId>ShrinkSDK.ModFramework</PackageId>
<Version>0.3.0</Version>
<Description>Engine-neutral ShrinkSDK mod contracts and lifecycle.</Description>
<Nullable>disable</Nullable>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\ShrinkModContext.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkModFramework\Runtime\Core\IShrinkMod.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkModFramework\Runtime\Core\ShrinkModBase.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkModFramework\Runtime\Core\ShrinkModHandle.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkModFramework\Runtime\Metadata\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkNetwork.Integration.EventBus</AssemblyName>
<RootNamespace>ShrinkNetwork.Integration</RootNamespace>
<PackageId>ShrinkSDK.Network.Integration.EventBus</PackageId>
<Version>0.2.0</Version>
<Description>ShrinkNetwork and EventBus generated bridge.</Description>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkNetwork.Integration.EventBus\*.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\ShrinkSDK.Context.Core\ShrinkSDK.Context.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
</ItemGroup>
</Project>
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkNetwork.Runtime</AssemblyName>
<RootNamespace>ShrinkNetwork</RootNamespace>
<PackageId>ShrinkSDK.Network</PackageId>
<Version>0.3.0</Version>
<Description>ShrinkSDK messaging, RPC, permission and transport runtime.</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkNetwork\Runtime\**\*.cs" />
<PackageReference Include="Kcp-CSharp" Version="1.0.8" />
<PackageReference Include="MessagePack" Version="3.1.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="UniTask" Version="2.5.10" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.1.0" PrivateAssets="compile;runtime;contentfiles;native" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>ShrinkRuntime.Abstractions</AssemblyName>
<RootNamespace>ShrinkSDK.Runtime</RootNamespace>
<PackageId>ShrinkSDK.Runtime.Abstractions</PackageId>
<Version>0.1.0</Version>
<Description>Engine-neutral ShrinkSDK runtime contracts.</Description>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkRuntime.Abstractions\Runtime\ShrinkRuntimeServices.cs" Link="Runtime\ShrinkRuntimeServices.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,89 @@
#nullable enable
using Godot;
namespace ShrinkTutorial.Godot;
public partial class ShrinkGodotTutorialOverlay : Control
{
private readonly Label _title = new();
private readonly Label _body = new();
private ShrinkTutorialRunner? _runner;
private Control? _target;
public override void _Ready()
{
SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
MouseFilter = MouseFilterEnum.Stop;
var panel = new PanelContainer { CustomMinimumSize = new Vector2(320, 100) };
panel.Position = new Vector2(24, 24);
var text = new VBoxContainer();
text.AddChild(_title);
text.AddChild(_body);
panel.AddChild(text);
AddChild(panel);
}
public void Bind(ShrinkTutorialRunner runner)
{
if (_runner != null) _runner.StepChanged -= ShowStep;
_runner = runner;
_runner.StepChanged += ShowStep;
if (_runner.CurrentStep != null) ShowStep(_runner.CurrentStep);
}
public override void _ExitTree()
{
if (_runner != null) _runner.StepChanged -= ShowStep;
_runner = null;
_target = null;
}
public override void _Process(double delta)
{
if (_runner?.CurrentStep is not { } step) return;
_target = ResolveTarget(step);
QueueRedraw();
}
public override void _Draw()
{
var viewport = GetViewportRect();
if (_target == null)
{
DrawRect(viewport, new Color(0, 0, 0, 0.72f));
return;
}
var hole = _target.GetGlobalRect().Grow(8);
var shade = new Color(0, 0, 0, 0.72f);
DrawRect(new Rect2(viewport.Position, new Vector2(viewport.Size.X, hole.Position.Y)), shade);
DrawRect(new Rect2(new Vector2(0, hole.End.Y), new Vector2(viewport.Size.X, viewport.Size.Y - hole.End.Y)), shade);
DrawRect(new Rect2(new Vector2(0, hole.Position.Y), new Vector2(hole.Position.X, hole.Size.Y)), shade);
DrawRect(new Rect2(new Vector2(hole.End.X, hole.Position.Y), new Vector2(viewport.Size.X - hole.End.X, hole.Size.Y)), shade);
}
public override void _GuiInput(InputEvent @event)
{
if (@event is not InputEventMouseButton { Pressed: true }) return;
var step = _runner?.CurrentStep;
if (step == null) return;
if (step.CompleteCondition == ShrinkTutorialCompleteCondition.AnyClick ||
step.CompleteCondition == ShrinkTutorialCompleteCondition.ClickTarget && _target?.GetGlobalRect().HasPoint(GetGlobalMousePosition()) == true)
_runner!.CompleteStep();
AcceptEvent();
}
private void ShowStep(ShrinkTutorialStep step)
{
_title.Text = step.Title;
_body.Text = step.Body;
Visible = true;
}
private Control? ResolveTarget(ShrinkTutorialStep step)
{
if (step.TargetMode == ShrinkTutorialTargetMode.Path) return GetTree().Root.GetNodeOrNull<Control>(step.Target);
if (step.TargetMode == ShrinkTutorialTargetMode.AnchorId) return GetTree().GetFirstNodeInGroup("shrink_tutorial_" + step.Target) as Control;
return null;
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkTutorial.Godot</AssemblyName>
<RootNamespace>ShrinkTutorial.Godot</RootNamespace>
<PackageId>ShrinkSDK.Tutorial.Godot</PackageId>
<Version>0.1.0</Version>
<Description>Godot Control overlay and Resources for ShrinkSDK Tutorial.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GodotSharp" Version="4.6.3" />
<ProjectReference Include="..\ShrinkSDK.Tutorial\ShrinkSDK.Tutorial.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
namespace ShrinkTutorial;
[Serializable]
public sealed class ShrinkTutorialStep
{
public string StepId = string.Empty;
public string Title = string.Empty;
public string Body = string.Empty;
public string Target = string.Empty;
public ShrinkTutorialTargetMode TargetMode;
public ShrinkTutorialCompleteCondition CompleteCondition = ShrinkTutorialCompleteCondition.ClickTarget;
public ShrinkTutorialDialogAnchor DialogAnchor = ShrinkTutorialDialogAnchor.Auto;
public ShrinkTutorialMaskShape MaskShape = ShrinkTutorialMaskShape.Rect;
public string CustomEventName = string.Empty;
public bool BlockInputOutsideTarget = true;
}
[Serializable]
public sealed class ShrinkTutorialData
{
public string TutorialId = string.Empty;
public List<ShrinkTutorialStep> Steps = new();
}
public sealed class ShrinkTutorialRunner
{
private readonly IShrinkTutorialStorage _storage;
private ShrinkTutorialProgress _progress;
public ShrinkTutorialRunner(IShrinkTutorialStorage storage)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
_progress = storage.Load() ?? new ShrinkTutorialProgress();
}
public ShrinkTutorialData Current { get; private set; }
public int StepIndex { get; private set; } = -1;
public ShrinkTutorialStep CurrentStep => Current != null && StepIndex >= 0 && StepIndex < Current.Steps.Count ? Current.Steps[StepIndex] : null;
public event Action<ShrinkTutorialStep> StepChanged;
public event Action<string> Completed;
public bool Start(ShrinkTutorialData tutorial)
{
if (tutorial == null || tutorial.Steps.Count == 0 || _progress.completedTutorials.Contains(tutorial.TutorialId)) return false;
Current = tutorial;
StepIndex = 0;
StepChanged?.Invoke(CurrentStep);
return true;
}
public bool CompleteStep(string customEventName = null)
{
var step = CurrentStep;
if (step == null) return false;
if (step.CompleteCondition == ShrinkTutorialCompleteCondition.CustomEvent &&
!string.Equals(step.CustomEventName, customEventName, StringComparison.Ordinal)) return false;
StepIndex++;
if (StepIndex < Current.Steps.Count) { StepChanged?.Invoke(CurrentStep); return true; }
_progress.completedTutorials.Add(Current.TutorialId);
_storage.Save(_progress);
var id = Current.TutorialId;
Current = null;
StepIndex = -1;
Completed?.Invoke(id);
return true;
}
public void Reset()
{
Current = null;
StepIndex = -1;
_storage.Reset();
_progress = new ShrinkTutorialProgress();
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkTutorial.Runtime</AssemblyName>
<RootNamespace>ShrinkTutorial</RootNamespace>
<PackageId>ShrinkSDK.Tutorial</PackageId>
<Version>0.2.0</Version>
<Description>Engine-neutral ShrinkSDK tutorial state and step runtime.</Description>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="Runtime\ShrinkTutorialCore.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkTutorial\Runtime\Core\ShrinkTutorialEnums.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkTutorial\Runtime\Core\ShrinkTutorialLocalization.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkTutorial\Runtime\Storage\IShrinkTutorialStorage.cs" />
<ProjectReference Include="..\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource path="res://SampleRoot.cs" type="Script" id="1"]
[node name="SampleRoot" type="Node"]
script = ExtResource("1")
@@ -0,0 +1,124 @@
#nullable enable
using System;
using System.Linq;
using System.Collections.Generic;
using Godot;
using ShrinkApp;
using ShrinkApp.Starter.Basic;
using ShrinkCommand;
using Cysharp.Threading.Tasks;
using ShrinkDataSaver;
using ShrinkEventBus;
using ShrinkNetwork;
using ShrinkSDK.Godot;
using ShrinkTutorial;
public partial class SampleRoot : ShrinkGodotHost
{
public override void _Ready()
{
base._Ready();
RunSmokeAsync().Forget();
}
private async UniTaskVoid RunSmokeAsync()
{
var marker = RequireWoven(GetType().Assembly);
using var binding = EventBus.Attach(new GodotSubscriber());
EventBus.Post(new GodotSmokeEvent());
var assembly = GetType().Assembly;
var registries = assembly.GetCustomAttributes(false).Select(value => value.GetType().Name).ToHashSet();
var savedValue = 42;
ShrinkDataSaverRuntime.Initialize(new ShrinkDataSaverRuntimeConfig
{
RootPath = PersistentDataPath,
CurrentSaveVersion = 1
});
ShrinkSave.RegisterModule("godot-smoke", () => savedValue, value => savedValue = value);
await ShrinkSave.SaveSlotAsync(0, new SaveOptions { SlotName = "Godot Smoke" });
savedValue = 0;
await ShrinkSave.LoadSlotAsync(0);
var app = await StartAppAsync();
var composition = ShrinkBasicComposition.CreateHost(app.Context.Services);
var entries = ShrinkBasicComposition.CreateEntries(app.Context.Services);
await composition.ApplyAsync(entries);
await composition.ApplyAsync(entries);
var activeFibers = composition.Runtime.Fibers.Count(fiber => fiber.State == ShrinkContext.ShrinkFiberState.Active);
var tutorialStorage = new MemoryTutorialStorage();
var tutorial = new ShrinkTutorialRunner(tutorialStorage);
var tutorialStarted = tutorial.Start(new ShrinkTutorialData
{
TutorialId = "godot-smoke",
Steps = { new ShrinkTutorialStep { StepId = "one", CompleteCondition = ShrinkTutorialCompleteCondition.AnyClick } }
});
var tutorialCompleted = tutorial.CompleteStep();
if (GodotSubscriber.Calls != 1 || GodotStaticSubscriber.Calls != 1 || savedValue != 42 ||
!app.IsRunning || !GodotInstaller.Initialized ||
activeFibers != 3 || !tutorialStarted || !tutorialCompleted || !tutorialStorage.Saved ||
!registries.Contains(nameof(ShrinkCommandStaticRegistryAttribute)) ||
!registries.Contains(nameof(ShrinkNetworkMessageRegistryAttribute)))
{
GD.PushError("ShrinkSDK Godot smoke failed.");
GetTree().Quit(1);
return;
}
await composition.ShutdownAsync();
var smokePath = System.IO.Path.Combine(OS.GetUserDataDir(), "shrink_smoke_pass.txt");
System.IO.File.WriteAllText(smokePath, $"CodeGen={marker.WeaverVersion};DataSaver={savedValue}");
if (!System.IO.File.Exists(smokePath))
throw new System.IO.IOException($"Failed to create smoke sentinel: {smokePath}");
GD.Print($"SHRINK_GODOT_SMOKE_PASS CodeGen={marker.WeaverVersion} DataSaver={savedValue}");
GetTree().Quit(0);
}
}
public sealed class MemoryTutorialStorage : IShrinkTutorialStorage
{
private ShrinkTutorialProgress _progress = new();
public bool Saved { get; private set; }
public ShrinkTutorialProgress Load() => _progress;
public void Save(ShrinkTutorialProgress progress) { _progress = progress; Saved = true; }
public void Reset() { _progress = new ShrinkTutorialProgress(); Saved = false; }
}
public readonly struct GodotSmokeEvent : IShrinkEvent { }
[ShrinkEventSubscriber]
public sealed class GodotSubscriber
{
public static int Calls;
[ShrinkSubscribe] private void Handle(GodotSmokeEvent value) => Calls++;
}
[ShrinkEventSubscriber]
public static class GodotStaticSubscriber
{
public static int Calls;
[ShrinkSubscribe] private static void Handle(GodotSmokeEvent value) => Calls++;
}
[ShrinkCommandSubscriber]
public static class GodotCommands
{
[ShrinkCommand("godot/smoke")] public static void Smoke() { }
}
[ShrinkNetworkMessage(42001, "godot/smoke")]
public sealed class GodotSmokeMessage : IShrinkNetworkMessage { }
[ShrinkAppModuleInstaller]
public sealed class GodotInstaller : IShrinkAppModuleInstaller
{
public static bool Initialized;
public string ModuleId => "godot-smoke";
public int Order => 0;
public IReadOnlyList<string> DependsOn => Array.Empty<string>();
public void RegisterServices(ShrinkAppContext context) => context.Services.Register(this);
public UniTask InitializeAsync(ShrinkAppContext context)
{
Initialized = true;
return UniTask.CompletedTask;
}
}
@@ -0,0 +1 @@
uid://j565ln7hmg6i
@@ -0,0 +1,20 @@
<Project Sdk="Godot.NET.Sdk/4.6.3">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
<_ShrinkCodeGenTaskAssembly>$(MSBuildThisFileDirectory)..\..\CodeGen\ShrinkSDK.CodeGen.Task\bin\$(Configuration)\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Packages\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Godot\ShrinkSDK.Godot.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.App.Starter.Basic\ShrinkSDK.App.Starter.Basic.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Tutorial\ShrinkSDK.Tutorial.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Tutorial.Godot\ShrinkSDK.Tutorial.Godot.csproj" />
<ProjectReference Include="..\..\CodeGen\ShrinkSDK.CodeGen.Task\ShrinkSDK.CodeGen.Task.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\..\CodeGen\ShrinkSDK.CodeGen.Task\buildTransitive\ShrinkSDK.CodeGen.targets" />
</Project>
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShrinkSDK.Godot.Sample", "ShrinkSDK.Godot.Sample.csproj", "{8ED6C49D-909C-4A5A-B80C-3537D1349377}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportRelease|Any CPU = ExportRelease|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8ED6C49D-909C-4A5A-B80C-3537D1349377}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://TutorialDemo.cs" id="1"]
[node name="TutorialDemo" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
@@ -0,0 +1,49 @@
#nullable enable
using Godot;
using ShrinkTutorial;
using ShrinkTutorial.Godot;
public partial class TutorialDemo : Control
{
public override void _Ready()
{
var target = new Button
{
Text = "Tutorial Target",
Position = new Vector2(480, 260),
CustomMinimumSize = new Vector2(220, 72)
};
target.AddToGroup("shrink_tutorial_primary");
AddChild(target);
var overlay = new ShrinkGodotTutorialOverlay();
AddChild(overlay);
var runner = new ShrinkTutorialRunner(new MemoryTutorialStorage());
runner.Start(new ShrinkTutorialData
{
TutorialId = "visual-smoke",
Steps =
{
new ShrinkTutorialStep
{
StepId = "target",
Title = "Godot Tutorial",
Body = "Target hole, dialog and input blocker smoke test",
TargetMode = ShrinkTutorialTargetMode.AnchorId,
Target = "primary",
CompleteCondition = ShrinkTutorialCompleteCondition.ClickTarget
}
}
});
overlay.Bind(runner);
GetTree().CreateTimer(2).Timeout += QuitCleanly;
}
private void QuitCleanly()
{
var tree = GetTree();
tree.UnloadCurrentScene();
tree.CreateTimer(0.1).Timeout += () => tree.Quit(0);
}
}
@@ -0,0 +1 @@
uid://bhjfn5prdv2pw
@@ -0,0 +1,52 @@
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../../Exports/Windows/ShrinkSDKGodot.exe"
script_export_mode=2
[preset.0.options]
binary_format/architecture="x86_64"
debug/export_console_wrapper=1
[preset.1]
name="Linux"
platform="Linux"
runnable=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../../Exports/Linux/ShrinkSDKGodot.x86_64"
script_export_mode=2
[preset.1.options]
binary_format/architecture="x86_64"
[preset.2]
name="macOS"
platform="macOS"
runnable=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../../Exports/macOS/ShrinkSDKGodot.zip"
script_export_mode=2
[preset.2.options]
application/bundle_identifier="work.crash.shrinksdk.sample"
binary_format/architecture="universal"
@@ -0,0 +1,30 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="ShrinkSDK Godot Sample"
run/main_scene="res://Main.tscn"
config/features=PackedStringArray("4.6", "C#")
[display]
window/size/viewport_width=960
window/size/viewport_height=540
[dotnet]
project/assembly_name="ShrinkSDK.Godot.Sample"
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
textures/vram_compression/import_etc2_astc=true
+45
View File
@@ -0,0 +1,45 @@
<Solution>
<Folder Name="/Packages/">
<Project Path="Packages/ShrinkSDK.Runtime.Abstractions/ShrinkSDK.Runtime.Abstractions.csproj" />
<Project Path="Packages/ShrinkSDK.Context.Core/ShrinkSDK.Context.Core.csproj" />
<Project Path="Packages/ShrinkSDK.EventBus/ShrinkSDK.EventBus.csproj" />
<Project Path="Packages/ShrinkSDK.Command/ShrinkSDK.Command.csproj" />
<Project Path="Packages/ShrinkSDK.Network/ShrinkSDK.Network.csproj" />
<Project Path="Packages/ShrinkSDK.Command.Integration.EventBus/ShrinkSDK.Command.Integration.EventBus.csproj" />
<Project Path="Packages/ShrinkSDK.Command.Integration.Network/ShrinkSDK.Command.Integration.Network.csproj" />
<Project Path="Packages/ShrinkSDK.Command.Integration.App/ShrinkSDK.Command.Integration.App.csproj" />
<Project Path="Packages/ShrinkSDK.DataSaver.Integration.EventBus/ShrinkSDK.DataSaver.Integration.EventBus.csproj" />
<Project Path="Packages/ShrinkSDK.DataSaver.Integration.App/ShrinkSDK.DataSaver.Integration.App.csproj" />
<Project Path="Packages/ShrinkSDK.Network.Integration.EventBus/ShrinkSDK.Network.Integration.EventBus.csproj" />
<Project Path="Packages/ShrinkSDK.Network.Integration.App/ShrinkSDK.Network.Integration.App.csproj" />
<Project Path="Packages/ShrinkSDK.DataSaver/ShrinkSDK.DataSaver.csproj" />
<Project Path="Packages/ShrinkSDK.App.Core/ShrinkSDK.App.Core.csproj" />
<Project Path="Packages/ShrinkSDK.Context.AppAdapter/ShrinkSDK.Context.AppAdapter.csproj" />
<Project Path="Packages/ShrinkSDK.App.Starter.Basic/ShrinkSDK.App.Starter.Basic.csproj" />
<Project Path="Packages/ShrinkSDK.ModFramework/ShrinkSDK.ModFramework.csproj" />
<Project Path="Packages/ShrinkSDK.ModFramework.Godot/ShrinkSDK.ModFramework.Godot.csproj" />
<Project Path="Packages/ShrinkSDK.Tutorial/ShrinkSDK.Tutorial.csproj" />
<Project Path="Packages/ShrinkSDK.Tutorial.Godot/ShrinkSDK.Tutorial.Godot.csproj" />
<Project Path="Packages/ShrinkSDK.Godot/ShrinkSDK.Godot.csproj" />
</Folder>
<Folder Name="/CodeGen/">
<Project Path="CodeGen/ShrinkSDK.CodeGen.Core/ShrinkSDK.CodeGen.Core.csproj" />
<Project Path="CodeGen/ShrinkSDK.CodeGen.Analyzers/ShrinkSDK.CodeGen.Analyzers.csproj" />
<Project Path="CodeGen/ShrinkSDK.CodeGen.Task/ShrinkSDK.CodeGen.Task.csproj" />
</Folder>
<Folder Name="/Tests/">
<Project Path="Tests/CodeGenFixture/CodeGenFixture.csproj" />
<Project Path="Tests/NuGetConsumer/NuGetConsumer.csproj" />
<Project Path="Tests/CodeGenValidation/CodeGenValidation.csproj" />
<Project Path="Tests/UnityAdapterCompile/UnityAdapterCompile.csproj" />
<Project Path="Tests/ModFixture/ModFixture.csproj" />
<Project Path="Tests/ModHostFixture/ModHostFixture.csproj" />
<Project Path="Tests/InstallerFixture/InstallerFixture.csproj" />
</Folder>
<Folder Name="/Samples/">
<Project Path="Samples/ShrinkSDK.Godot.Sample/ShrinkSDK.Godot.Sample.csproj" />
</Folder>
<Folder Name="/Installer/">
<Project Path="Installer/ShrinkSDK.Godot.Installer.csproj" />
</Folder>
</Solution>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGenFixture</AssemblyName>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
<SignAssembly Condition="'$(FixtureSignAssembly)' == 'true'">true</SignAssembly>
<AssemblyOriginatorKeyFile Condition="'$(FixtureSignAssembly)' == 'true'">$(FixtureStrongNameKeyFile)</AssemblyOriginatorKeyFile>
<_ShrinkCodeGenTaskAssembly>$(MSBuildThisFileDirectory)..\..\CodeGen\ShrinkSDK.CodeGen.Task\bin\$(Configuration)\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Packages\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Network.Integration.EventBus\ShrinkSDK.Network.Integration.EventBus.csproj" />
<ProjectReference Include="..\..\CodeGen\ShrinkSDK.CodeGen.Task\ShrinkSDK.CodeGen.Task.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\..\CodeGen\ShrinkSDK.CodeGen.Task\buildTransitive\ShrinkSDK.CodeGen.targets" />
</Project>
+79
View File
@@ -0,0 +1,79 @@
#nullable enable
using System;
using System.Linq;
using ShrinkCommand;
using ShrinkEventBus;
using ShrinkNetwork;
using ShrinkNetwork.Integration;
using ShrinkSDK.Runtime;
var assembly = typeof(Program).Assembly;
var marker = assembly.GetCustomAttributes(typeof(ShrinkCodeGenWovenAttribute), false)
.Cast<ShrinkCodeGenWovenAttribute>()
.SingleOrDefault() ?? throw new InvalidOperationException("CodeGen marker is missing.");
if (marker.WeaverVersion != "0.1.0" || !Guid.TryParse(marker.InputMvid, out _))
throw new InvalidOperationException("CodeGen marker is invalid.");
if (!typeof(InstanceSubscriber).GetInterfaces().Contains(typeof(IShrinkGeneratedSubscriber)))
throw new InvalidOperationException("Generated subscriber interface is missing.");
using var binding = EventBus.Attach(new InstanceSubscriber());
EventBus.Post(new FixtureEvent());
if (InstanceSubscriber.Calls != 1 || StaticSubscriber.Calls != 1)
throw new InvalidOperationException($"Generated EventBus bindings failed: instance={InstanceSubscriber.Calls}, static={StaticSubscriber.Calls}.");
if (assembly.GetCustomAttributes(typeof(ShrinkCommandStaticRegistryAttribute), false).Length != 1)
throw new InvalidOperationException("Command registry metadata is missing.");
if (assembly.GetCustomAttributes(typeof(ShrinkNetworkMessageRegistryAttribute), false).Length != 1)
throw new InvalidOperationException("Network message registry metadata is missing.");
if (assembly.GetCustomAttributes(typeof(ShrinkNetworkStaticSubscriberRegistryAttribute), false).Length != 1)
throw new InvalidOperationException("Network subscriber registry metadata is missing.");
var bridgeRegistrations = (System.Collections.IEnumerable)(typeof(ShrinkNetworkEventRegistry)
.GetMethod("Snapshot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)!
.Invoke(null, null) ?? throw new InvalidOperationException("Network EventBus registry snapshot failed."));
if (!bridgeRegistrations.Cast<object>().Any(item => item.GetType().GetProperty("EventType")?.GetValue(item) as Type == typeof(FixtureNetworkEvent)))
throw new InvalidOperationException("Generated Network/EventBus binding is missing.");
Console.WriteLine($"PASS CodeGen {marker.WeaverVersion}: EventBus + Command + Network + Network/EventBus registries");
public readonly struct FixtureEvent : IShrinkEvent { }
[ShrinkEventSubscriber]
public sealed class InstanceSubscriber
{
public static int Calls;
[ShrinkSubscribe]
private void OnEvent(FixtureEvent value) => Calls++;
}
[ShrinkEventSubscriber]
public static class StaticSubscriber
{
public static int Calls;
[ShrinkSubscribe]
private static void OnEvent(FixtureEvent value) => Calls++;
}
[ShrinkCommandSubscriber]
public static class FixtureCommands
{
[ShrinkCommand("fixture/ping")]
public static void Ping() { }
}
[ShrinkNetworkMessage(41001, "fixture/ping")]
public sealed class FixtureMessage : IShrinkNetworkMessage { }
[ShrinkNetworkEvent]
[ShrinkNetworkMessage(41002, "fixture/event")]
public sealed class FixtureNetworkEvent : IShrinkEvent, IShrinkNetworkMessage { }
[ShrinkNetworkSubscriber]
public static class FixtureNetworkHandlers
{
[ShrinkNetworkSubscribe]
public static void Handle(FixtureMessage message) { }
}
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGenValidation</AssemblyName>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\CodeGen\ShrinkSDK.CodeGen.Core\ShrinkSDK.CodeGen.Core.csproj" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
</Project>
+163
View File
@@ -0,0 +1,163 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Mono.Cecil;
using ShrinkSDK.CodeGen;
if (args.Length == 2 && args[0] == "--marker")
{
using var exported = AssemblyDefinition.ReadAssembly(args[1]);
var marker = exported.CustomAttributes.SingleOrDefault(attribute =>
attribute.AttributeType.FullName == "ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute");
Require(marker != null, "exported assembly has no ShrinkSDK CodeGen marker");
Require(exported.MainModule.Types.Any(type => type.FullName ==
"ShrinkEventBus.Generated.ShrinkGeneratedStaticBindings"),
"exported assembly has no static EventBus bootstrap");
Console.WriteLine($"PASS exported assembly woven by {marker!.ConstructorArguments[0].Value}");
return;
}
if ((args.Length == 3 || args.Length == 4) && args[0] == "--resign")
{
var signedAssembly = Path.GetFullPath(args[1]);
var keepOutput = args.Length == 4;
var output = keepOutput
? Path.GetFullPath(args[3])
: Path.Combine(Path.GetTempPath(), "ShrinkSDK.CodeGen.Resigned." + Guid.NewGuid().ToString("N") + ".dll");
var outputPdb = Path.ChangeExtension(output, ".pdb");
try
{
var result = ShrinkAssemblyWeaver.Weave(signedAssembly, Path.ChangeExtension(signedAssembly, ".pdb"),
BuildReferences(Path.GetDirectoryName(signedAssembly)!), output, outputPdb,
ShrinkCodeGenPlatform.EngineNeutral, args[2]);
Require(result.Succeeded && result.Changed,
"signed assembly re-weave failed: " + string.Join(" | ", result.Diagnostics.Select(item => item.Message)));
using var resigned = AssemblyDefinition.ReadAssembly(output);
Require(resigned.Name.HasPublicKey, "re-signed output has no public key");
Console.WriteLine("PASS signed assembly woven and re-signed with explicit key");
}
finally
{
if (!keepOutput)
{
if (File.Exists(output)) File.Delete(output);
if (File.Exists(outputPdb)) File.Delete(outputPdb);
}
}
return;
}
if (args.Length != 1 || !File.Exists(args[0]))
throw new ArgumentException("Pass the unwoven CodeGenFixture assembly path.");
var sourceAssembly = Path.GetFullPath(args[0]);
var sourceDirectory = Path.GetDirectoryName(sourceAssembly)!;
var sourcePdb = Path.ChangeExtension(sourceAssembly, ".pdb");
var references = BuildReferences(sourceDirectory);
var root = Path.Combine(Path.GetTempPath(), "ShrinkSDK.CodeGen.Validation", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
try
{
var woven = Path.Combine(root, "fixture.woven.dll");
var wovenPdb = Path.Combine(root, "fixture.woven.pdb");
var first = ShrinkAssemblyWeaver.Weave(sourceAssembly, sourcePdb, references, woven, wovenPdb);
Require(first.Succeeded && first.Changed,
"single weave did not change the assembly: " + string.Join(" | ", first.Diagnostics.Select(item => item.Message)));
Require(first.InstanceSubscribers == 1 && first.StaticSubscribers == 1 && first.RegistryEntries == 5,
$"unexpected generated counts: {first.InstanceSubscribers}/{first.StaticSubscribers}/{first.RegistryEntries}");
VerifyStructure(woven);
var second = ShrinkAssemblyWeaver.Weave(woven, wovenPdb, references.Append(woven),
Path.Combine(root, "twice.dll"), Path.Combine(root, "twice.pdb"));
Require(second.Succeeded && !second.Changed, "repeated weave was not idempotent");
var corruptPdb = Path.Combine(root, "corrupt.pdb");
File.WriteAllText(corruptPdb, "not a portable pdb");
var corrupt = ShrinkAssemblyWeaver.Weave(sourceAssembly, corruptPdb, references,
Path.Combine(root, "corrupt.dll"), Path.Combine(root, "corrupt.out.pdb"));
Require(!corrupt.Succeeded && !File.Exists(Path.Combine(root, "corrupt.dll")),
"damaged PDB was not rejected atomically");
var missing = ShrinkAssemblyWeaver.Weave(sourceAssembly, null, Array.Empty<string>(),
Path.Combine(root, "missing.dll"), null);
Require(!missing.Succeeded, "missing dependencies were not rejected");
var signedInput = Path.Combine(root, "signed.dll");
using (var assembly = AssemblyDefinition.ReadAssembly(sourceAssembly))
{
assembly.Name.PublicKey = Enumerable.Range(0, 160).Select(value => (byte)(value + 1)).ToArray();
assembly.Name.Attributes |= AssemblyAttributes.PublicKey;
assembly.Write(signedInput);
}
var signed = ShrinkAssemblyWeaver.Weave(signedInput, null, references,
Path.Combine(root, "signed.out.dll"), null);
Require(!signed.Succeeded && signed.Diagnostics.Any(item => item.Message.Contains("Signed assemblies")),
"signed assembly was not rejected");
var parallel = Enumerable.Range(0, 4).Select(index => Task.Run(() =>
{
var output = Path.Combine(root, $"parallel-{index}.dll");
var outputPdb = Path.Combine(root, $"parallel-{index}.pdb");
return ShrinkAssemblyWeaver.Weave(sourceAssembly, sourcePdb, references, output, outputPdb);
})).ToArray();
await Task.WhenAll(parallel);
Require(parallel.All(task => task.Result.Succeeded && task.Result.Changed),
"parallel weaving of independent assemblies failed");
Console.WriteLine("PASS Cecil structure + single/repeat/parallel weave + corrupt PDB/signed/missing dependency guards");
}
finally
{
Directory.Delete(root, true);
}
static void VerifyStructure(string path)
{
using var assembly = AssemblyDefinition.ReadAssembly(path);
var module = assembly.MainModule;
var instance = module.Types.Single(type => type.Name == "InstanceSubscriber");
Require(instance.Interfaces.Any(item => item.InterfaceType.FullName == "ShrinkEventBus.IShrinkGeneratedSubscriber"),
"generated subscriber interface is missing");
Require(instance.Methods.Any(method => method.Name == "ShrinkEventBus.IShrinkGeneratedSubscriber.AttachGenerated"),
"AttachGenerated is missing");
Require(module.Types.Any(type => type.FullName == "ShrinkEventBus.Generated.ShrinkGeneratedStaticBindings"),
"static binding bootstrap is missing");
Require(module.Types.Any(type => type.FullName ==
"ShrinkNetwork.Integration.Generated.ShrinkGeneratedNetworkEventBindings"),
"Network/EventBus generated bootstrap is missing");
var moduleInitializer = module.Types.Single(type => type.Name == "<Module>").Methods.Single(method => method.Name == ".cctor");
Require(moduleInitializer.Body.Instructions.Any(instruction => instruction.Operand is MethodReference method && method.Name == "Register"),
"module initializer does not call static registration");
var attributes = assembly.CustomAttributes.Select(attribute => attribute.AttributeType.FullName).ToArray();
foreach (var expected in new[]
{
"ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute",
"ShrinkCommand.ShrinkCommandStaticRegistryAttribute",
"ShrinkNetwork.ShrinkNetworkMessageRegistryAttribute",
"ShrinkNetwork.ShrinkNetworkStaticSubscriberRegistryAttribute"
})
Require(attributes.Contains(expected), $"assembly registry is missing: {expected}");
}
static void Require(bool condition, string message)
{
if (!condition) throw new InvalidOperationException(message);
}
static string[] BuildReferences(string sourceDirectory)
{
var referencePackRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".nuget", "packages", "microsoft.netcore.app.ref");
var referencePack = Directory.GetDirectories(referencePackRoot)
.Select(path => Path.Combine(path, "ref", "net8.0"))
.Where(Directory.Exists)
.OrderByDescending(path => path, StringComparer.OrdinalIgnoreCase)
.First();
return Directory.GetFiles(sourceDirectory, "*.dll")
.Concat(Directory.GetFiles(referencePack, "*.dll"))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.InstallerFixture</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Installer\ShrinkSDK.Godot.Installer.csproj" />
</ItemGroup>
</Project>
+65
View File
@@ -0,0 +1,65 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using ShrinkSDK.Godot.Editor;
using ShrinkSDK.Installer;
var root = Path.Combine(Path.GetTempPath(), "ShrinkSDK.InstallerFixture", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
try
{
var project = Path.Combine(root, "Fixture.csproj");
File.WriteAllText(project, """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net8.0</TargetFramework><FixtureValue>keep</FixtureValue></PropertyGroup>
<ItemGroup>
<PackageReference Include="Existing.Package" Version="1.2.3" />
<PackageReference Include="Child.Version.Package"><Version>3.4.5</Version></PackageReference>
</ItemGroup>
</Project>
""");
var config = Path.Combine(root, "NuGet.Config");
File.WriteAllText(config, """
<configuration><packageSources><add key="Existing" value="https://example.invalid/v3/index.json" /></packageSources></configuration>
""");
ShrinkProjectPackageEditor.SetPackageReference(project, "ShrinkSDK.EventBus", "2.1.0");
ShrinkProjectPackageEditor.SetPackageReference(project, "ShrinkSDK.EventBus", "2.1.0");
var installed = XDocument.Load(project);
Require(installed.Descendants("PackageReference").Count(element =>
(string?)element.Attribute("Include") == "ShrinkSDK.EventBus") == 1, "package reference was duplicated");
Require(installed.ToString().Contains("Existing.Package") && installed.ToString().Contains("FixtureValue"),
"unrelated project content was changed");
var installedVersions = ShrinkProjectPackageEditor.ReadPackageReferences(project);
Require(installedVersions["ShrinkSDK.EventBus"] == "2.1.0", "installed version was not read correctly");
Require(installedVersions["Child.Version.Package"] == "3.4.5", "child Version element was not read correctly");
Require(ShrinkPackageVersion.Classify("2.2.0", "2.1.0") == ShrinkPackageVersionRelation.Newer,
"newer installed version was classified as outdated");
Require(ShrinkPackageVersion.Classify("2.1.0-preview.10", "2.1.0-preview.2") ==
ShrinkPackageVersionRelation.Newer, "prerelease versions were not ordered semantically");
ShrinkProjectPackageEditor.SetPackageReference(project, "ShrinkSDK.EventBus", null);
Require(!XDocument.Load(project).Descendants("PackageReference").Any(element =>
(string?)element.Attribute("Include") == "ShrinkSDK.EventBus"), "package reference was not removed");
ShrinkProjectPackageEditor.EnsureShrinkFeed(config);
ShrinkProjectPackageEditor.EnsureShrinkFeed(config);
var sources = XDocument.Load(config).Descendants("add").ToArray();
Require(sources.Count(element => (string?)element.Attribute("key") == "ShrinkSDK") == 1,
"ShrinkSDK feed was duplicated");
Require(sources.Any(element => (string?)element.Attribute("key") == "Existing"),
"existing NuGet source was removed");
Console.WriteLine("PASS Godot Installer atomic package/feed install-update-remove merge");
}
finally
{
Directory.Delete(root, true);
}
static void Require(bool condition, string message)
{
if (!condition) throw new InvalidOperationException(message);
}
@@ -0,0 +1,7 @@
using ShrinkNetwork;
[ShrinkNetworkMessage(49999, "duplicate")]
public sealed class DuplicateMessageA : IShrinkNetworkMessage { }
[ShrinkNetworkMessage(49999, "duplicate")]
public sealed class DuplicateMessageB : IShrinkNetworkMessage { }
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.InvalidCodeGenFixture</AssemblyName>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
<RunAnalyzers>false</RunAnalyzers>
<_ShrinkCodeGenTaskAssembly>$(MSBuildThisFileDirectory)..\..\CodeGen\ShrinkSDK.CodeGen.Task\bin\$(Configuration)\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Packages\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Network\ShrinkSDK.Network.csproj" />
<ProjectReference Include="..\..\CodeGen\ShrinkSDK.CodeGen.Task\ShrinkSDK.CodeGen.Task.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\..\CodeGen\ShrinkSDK.CodeGen.Task\buildTransitive\ShrinkSDK.CodeGen.targets" />
</Project>
+27
View File
@@ -0,0 +1,27 @@
using ShrinkCommand;
using ShrinkEventBus;
using ShrinkModFramework;
[ShrinkMod("fixture.mod", "Fixture Mod", "1.0.0")]
public sealed class FixtureMod : ShrinkModBase, IShrinkModUnload
{
public bool Ready { get; private set; }
public int UnloadCalls { get; private set; }
public override void OnReady(ShrinkModContext context) => Ready = true;
public void OnUnload(ShrinkModContext context) => UnloadCalls++;
}
public readonly struct ModFixtureEvent : IShrinkEvent { }
[ShrinkEventSubscriber]
public static class ModFixtureEvents
{
public static int Calls { get; private set; }
[ShrinkSubscribe] private static void Handle(ModFixtureEvent value) => Calls++;
}
[ShrinkCommandSubscriber]
public static class ModFixtureCommands
{
[ShrinkCommand("mod-fixture/ping")] public static void Ping() { }
}
+15
View File
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>ShrinkSDK.ModFixture</AssemblyName>
<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>
<_ShrinkCodeGenTaskAssembly>$(MSBuildThisFileDirectory)..\..\CodeGen\ShrinkSDK.CodeGen.Task\bin\$(Configuration)\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Packages\ShrinkSDK.Runtime.Abstractions\ShrinkSDK.Runtime.Abstractions.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.ModFramework\ShrinkSDK.ModFramework.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
</ItemGroup>
<Import Project="..\..\CodeGen\ShrinkSDK.CodeGen.Task\buildTransitive\ShrinkSDK.CodeGen.targets" />
</Project>
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.ModHostFixture</AssemblyName>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Packages\ShrinkSDK.ModFramework.Godot\ShrinkSDK.ModFramework.Godot.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.EventBus\ShrinkSDK.EventBus.csproj" />
<ProjectReference Include="..\..\Packages\ShrinkSDK.Command\ShrinkSDK.Command.csproj" />
</ItemGroup>
</Project>
+37
View File
@@ -0,0 +1,37 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using ShrinkCommand;
using ShrinkEventBus;
using ShrinkModFramework.Godot;
if (args.Length != 1 || !File.Exists(args[0]))
throw new ArgumentException("Pass the woven mod assembly path.");
using var loader = new ShrinkGodotModLoader();
var instances = loader.Load(args[0]);
if (instances.Count != 1) throw new InvalidOperationException("Mod entry was not discovered.");
var mod = instances[0];
var type = mod.GetType();
if (type.GetProperty("Ready")?.GetValue(mod) is not true)
throw new InvalidOperationException("Mod lifecycle did not reach OnReady.");
var assembly = type.Assembly;
if (assembly.GetCustomAttributes(typeof(ShrinkCommandStaticRegistryAttribute), false).Length != 1)
throw new InvalidOperationException("Mod command registry metadata is missing.");
var eventType = assembly.GetType("ModFixtureEvent") ?? throw new InvalidOperationException("Mod event is missing.");
var post = typeof(EventBus).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Single(method => method.Name == "Post" && method.IsGenericMethodDefinition && method.GetParameters().Length == 1)
.MakeGenericMethod(eventType);
post.Invoke(null, new[] { Activator.CreateInstance(eventType) });
var eventSubscriber = assembly.GetType("ModFixtureEvents") ?? throw new InvalidOperationException("Mod static subscriber is missing.");
if ((int?)eventSubscriber.GetProperty("Calls", BindingFlags.Public | BindingFlags.Static)?.GetValue(null) != 1)
throw new InvalidOperationException("Mod static EventBus binding did not run.");
if (!loader.Unload(args[0])) throw new InvalidOperationException("Mod unload failed.");
if ((int?)type.GetProperty("UnloadCalls")?.GetValue(mod) != 1)
throw new InvalidOperationException("Mod OnUnload was not called.");
if (loader.LoadedAssemblyPaths.Count != 0)
throw new InvalidOperationException("Mod remained registered after unload.");
Console.WriteLine("PASS external netstandard2.1 mod CodeGen + Godot AssemblyLoadContext lifecycle");
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.NuGetConsumer</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ShrinkSDK.EventBus" Version="2.1.0" />
<PackageReference Include="ShrinkSDK.Command" Version="0.3.0" />
<PackageReference Include="ShrinkSDK.Network" Version="0.3.0" />
</ItemGroup>
</Project>
+46
View File
@@ -0,0 +1,46 @@
#nullable enable
using System;
using System.Linq;
using ShrinkCommand;
using ShrinkEventBus;
using ShrinkNetwork;
using ShrinkSDK.Runtime;
var assembly = typeof(Program).Assembly;
var marker = assembly.GetCustomAttributes(typeof(ShrinkCodeGenWovenAttribute), false)
.Cast<ShrinkCodeGenWovenAttribute>().SingleOrDefault()
?? throw new InvalidOperationException("buildTransitive CodeGen did not run.");
using var binding = EventBus.Attach(new NuGetSubscriber());
EventBus.Post(new NuGetEvent());
if (NuGetSubscriber.Calls != 1 || NuGetStaticSubscriber.Calls != 1)
throw new InvalidOperationException("NuGet EventBus weaving failed.");
if (assembly.GetCustomAttributes(typeof(ShrinkCommandStaticRegistryAttribute), false).Length != 1 ||
assembly.GetCustomAttributes(typeof(ShrinkNetworkMessageRegistryAttribute), false).Length != 1)
throw new InvalidOperationException("NuGet registries are missing.");
Console.WriteLine($"PASS NuGet buildTransitive CodeGen {marker.WeaverVersion}");
public readonly struct NuGetEvent : IShrinkEvent { }
[ShrinkEventSubscriber]
public sealed class NuGetSubscriber
{
public static int Calls;
[ShrinkSubscribe] private void Handle(NuGetEvent value) => Calls++;
}
[ShrinkEventSubscriber]
public static class NuGetStaticSubscriber
{
public static int Calls;
[ShrinkSubscribe] private static void Handle(NuGetEvent value) => Calls++;
}
[ShrinkCommandSubscriber]
public static class NuGetCommands
{
[ShrinkCommand("nuget/smoke")] public static void Smoke() { }
}
[ShrinkNetworkMessage(43001, "nuget/smoke")]
public sealed class NuGetMessage : IShrinkNetworkMessage { }
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkSDK.UnityAdapterCompile</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkShared.CodeGen\Editor\Core\*.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkShared.CodeGen\Editor\UnityShrinkCodeGenAdapter.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkShared.CodeGen\Editor\ShrinkRegistryILPostProcessor.cs" />
<Compile Include="..\..\..\Assets\Modules\ShrinkEventBus\CodeGen\Editor\EventBusILPostProcessor.cs" />
<Reference Include="Unity.CompilationPipeline.Common">
<HintPath>D:\UnityEditor\2022.3.62f3\Editor\Data\Managed\Unity.CompilationPipeline.Common.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Mono.Cecil">
<HintPath>..\..\..\Library\PackageCache\com.unity.nuget.mono-cecil@1.11.4\Mono.Cecil.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>
+4
View File
@@ -43,3 +43,7 @@ https://git.crash.work/ShrinkSDK/Installer.git#v0.2.4
随后在 Unity 打开 `ShrinkSDK/包管理`。UI Toolkit 窗口会显示直接与间接依赖的真实安装状态,并按 Context 组合基础、应用宿主、独立功能和模块集成组织固定版本包与推荐组合。安装器只合并 `com.cneicy` registry 和依赖数据,不写认证信息,也不复制模板到 `Assets`
发布、子模块迁移和独立开发宿主生成规则位于 [Tools/RepositoryMigration/Initialize-ShrinkSdkPackageRepositories.ps1](Tools/RepositoryMigration/Initialize-ShrinkSdkPackageRepositories.ps1)。架构约束与跨包验证要求见 [DESIGN.md](DESIGN.md)。
## 第三方与合规
概念参考(NeoForge / Minecraft Forge、MessagePipe)与随仓库分发的第三方二进制(Kcp-CSharp.dll 等)的许可证遵从情况见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。
+89
View File
@@ -0,0 +1,89 @@
# 第三方声明(THIRD-PARTY NOTICES
本文件说明 ShrinkSDK Workspace 的第三方概念参考、依赖与对应许可证遵从情况。最后核对日期:2026-08-29。
## 结论速览
| 对象 | 关系 | 许可证 | 义务 |
| --- | --- | --- | --- |
| NeoForge / Minecraft Forge | 设计概念参考(未复制代码) | LGPL-2.1 | 无强制义务,本文档致谢 |
| MessagePipeCysharp | 设计概念参考(未复制代码、未引入包) | MIT | 无强制义务,本文档致谢 |
| AlicizaXGitHub 组织) | 无引用 | MIT / Apache-2.0 | 无 |
| Kcp-CSharp.dll | 内嵌二进制(`Assets/Modules/ShrinkNetwork/Plugins/`) | MIT | 保留版权与许可声明(见下) |
| System.Runtime.CompilerServices.Unsafe.dll | 内嵌二进制(`Assets/Modules/ShrinkNetwork/Plugins/`) | MIT | 保留版权与许可声明(见下) |
| UniTask | 包管理器依赖(不随本仓库再分发) | MIT | 许可文本随包分发 |
| MessagePack | 包管理器依赖(不随本仓库再分发) | MIT | 许可文本随包分发 |
| Newtonsoft.Json | 包管理器依赖(不随本仓库再分发) | MIT | 许可文本随包分发 |
## 概念参考归致
### NeoForge / Minecraft ForgeLGPL-2.1
`ShrinkEventBus``ShrinkModFramework` 在 API 语义与设计思路上参考了 Forge / NeoForge
- 五级订阅优先级 `ShrinkEventPriority.Highest / High / Normal / Low / Lowest` 对应 Forge `EventPriority`
- `[ShrinkSubscribe(receiveCanceled: ...)]` 对应 `@SubscribeEvent(receiveCanceled = ...)`
- `EventResult { DEFAULT, ALLOW, DENY }` 对应 Forge `Event.Result`
- `IShrinkEventBus.Attach(object)` 的对象注册范式对应 `MinecraftForge.EVENT_BUS.register(...)`
以上为 API 概念与命名语义层面的借鉴。实现(IL 后处理静态绑定、泛型静态槽位通道、UniTask 调度集成、写时复制 handler 快照等)均为本项目原创,未复制 NeoForge / Minecraft Forge 的任何源代码,因此不构成 LGPL-2.1 意义上的衍生作品。该边界与 `Docs/JustAnyProjectArchitectureAudit.md`A.3 参考边界)的既有声明一致。
### MessagePipeMIT
MessagePipe 未作为依赖引入,EventBus 也不提供 `IPublisher/ISubscriber` 拆分或 DI 集成,与 MessagePipe 的 API 形态不构成高度相似。仅借鉴其"独立 broker + 稳定 handler 快照 + 低分配发布路径"的实现思路(见 `Docs/JustAnyProjectArchitectureAudit.md` A.3),不构成代码衍生。
### AlicizaX
本仓库及全部子模块未引用、未复制、未派生 https://github.com/AlicizaX 下任何仓库的代码或资产。
## 随本仓库分发的第三方二进制
以下 DLL 直接存放在 `Assets/Modules/ShrinkNetwork/Plugins/`,随仓库与构建产物分发,按 MIT 要求保留其版权与许可声明:
### Kcp-CSharp.dll
- 来源:https://github.com/Molth/Kcp-CSharp NuGet 包 `Kcp-CSharp` 1.0.8
- 用途:`ShrinkNetwork` KCP 传输层
- 许可证:MITCopyright (c) 2024 Nevin
- 上游协议实现 KCPhttps://github.com/skywind3000/kcp MITCopyright (c) 2017 Lin Wei (skywind3000 at gmail.com)
### System.Runtime.CompilerServices.Unsafe.dll
- 来源:NuGet 包 `System.Runtime.CompilerServices.Unsafe`
- 许可证:MITCopyright (c) .NET Foundation and Contributors
### MIT 许可证全文(适用于上述两个组件)
```text
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## 包管理器依赖(不随本仓库再分发)
以下依赖通过 Unity Package Manager 或 NuGet 引入,许可证文本随各自包分发,此处仅作登记:
- UniTask 2.5.10 — MIT — Copyright (c) 2019 Yoshifumi Kawai / Cysharp, Inc. — https://github.com/Cysharp/UniTask
- MessagePack 3.1.4 — MIT — Copyright (c) 2017 Yoshifumi Kawai and contributors — https://github.com/MessagePack-CSharp/MessagePack-CSharp
- Newtonsoft.Json 13.0.3 — MIT — Copyright (c) 2007 James Newton-King — https://github.com/JamesNK/Newtonsoft.Json
- Unity 官方包 — 按 Unity 各自许可条款分发
## 维护者注意
- `ShrinkNetwork` 模块独立发布时,应把"随本仓库分发的第三方二进制"一节同步进该模块仓库(模块是独立公开仓库,根仓库声明不能覆盖其独立分发路径)。
- Mod SDK 导出器(`ShrinkModSdkExporter`)目前只导出 ShrinkSDK 自有程序集;若未来把上述第三方 DLL 一并打包进导出包,必须在导出包内附带对应许可声明。