This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
name: Publish UPM package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Fetch tagged revision
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
ref="${{ gitea.sha }}"
|
||||||
|
test -n "$ref"
|
||||||
|
git init .
|
||||||
|
git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkApp.Starter.Basic.git"
|
||||||
|
git fetch --depth=1 origin "$ref"
|
||||||
|
git checkout --detach FETCH_HEAD
|
||||||
|
|
||||||
|
- name: Validate immutable release version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
tag="$(git describe --exact-match --tags HEAD)"
|
||||||
|
version="$(node -p "require('./package.json').version")"
|
||||||
|
test "$tag" = "v$version"
|
||||||
|
npm pack --dry-run
|
||||||
|
|
||||||
|
- name: Publish to ShrinkSDK registry
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
: "${NODE_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}"
|
||||||
|
npmrc="$HOME/.npmrc"
|
||||||
|
cleanup() { rm -f "$npmrc"; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
printf '%s\n' \
|
||||||
|
'registry=https://git.crash.work/api/packages/ShrinkSDK/npm/' \
|
||||||
|
'//git.crash.work/api/packages/ShrinkSDK/npm/:_authToken=${NODE_AUTH_TOKEN}' > "$npmrc"
|
||||||
|
npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Verify standalone Unity package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
editmode:
|
||||||
|
runs-on: unity-2022.3.62f3
|
||||||
|
container:
|
||||||
|
image: docker.1panel.live/unityci/editor:ubuntu-2022.3.62f3-windows-mono-3
|
||||||
|
volumes:
|
||||||
|
- /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-license:/root/.local/share/unity3d/Unity
|
||||||
|
- /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-entitlements:/root/.config/unity3d/Unity/licenses
|
||||||
|
steps:
|
||||||
|
- name: Fetch selected revision
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
ref="${{ gitea.sha }}"
|
||||||
|
git init .
|
||||||
|
git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkApp.Starter.Basic.git"
|
||||||
|
git fetch --depth=1 origin "$ref"
|
||||||
|
git checkout --detach FETCH_HEAD
|
||||||
|
|
||||||
|
- name: Run package EditMode tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
unity_bin="$(command -v unity-editor || command -v unity || command -v Unity || true)"
|
||||||
|
test -n "$unity_bin"
|
||||||
|
"$unity_bin" \
|
||||||
|
-batchmode \
|
||||||
|
-nographics \
|
||||||
|
-quit \
|
||||||
|
-projectPath "$PWD/Development~/UnityProject" \
|
||||||
|
-runTests \
|
||||||
|
-testPlatform EditMode \
|
||||||
|
-testResults "$PWD/TestResults/editmode.xml" \
|
||||||
|
-logFile "$PWD/TestResults/unity.log"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/Development~/UnityProject/[Ll]ibrary/
|
||||||
|
/Development~/UnityProject/[Tt]emp/
|
||||||
|
/Development~/UnityProject/[Oo]bj/
|
||||||
|
/Development~/UnityProject/[Ll]ogs/
|
||||||
|
/Development~/UnityProject/[Uu]ser[Ss]ettings/
|
||||||
|
/Development~/UnityProject/TestResults/
|
||||||
|
/Tools~/**/[Bb]in/
|
||||||
|
/Tools~/**/[Oo]bj/
|
||||||
|
*.user
|
||||||
|
*.DotSettings.user
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.git/
|
||||||
|
.gitea/
|
||||||
|
Development~/
|
||||||
|
Tools~/
|
||||||
|
*.csproj
|
||||||
|
*.sln
|
||||||
|
*.user
|
||||||
|
*.DotSettings.user
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
本文件记录 `ShrinkApp.Starter.Basic` 的包内变更。
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-05-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 新增最小起盘 Starter 包。
|
||||||
|
- 新增场景生成器 `ShrinkAppBasicStarterGenerator`。
|
||||||
|
- 新增示例控制器 `ShrinkAppBasicSampleController`。
|
||||||
|
- 新增 `ShrinkAppBasicDemoRuntime`,补齐默认命令源、状态命令与最小 loopback 网络示例。
|
||||||
|
- 新增 `ShrinkAppBasicStarterSettings`,提供最小 Starter 级别的默认配置资产。
|
||||||
|
- 示例场景继续扩展为小型调试台,补齐命令输入、输出日志、模块/服务/存档/命令/网络状态面板。
|
||||||
|
- 状态面板进一步细化为已启动模块、已注册服务、存档槽位、命令列表、网络诊断五块只读视图。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3e78d4720b16b84fbf0d766b0a0122b
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Ll]ibrary/
|
||||||
|
[Tt]emp/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]ogs/
|
||||||
|
[Uu]ser[Ss]ettings/
|
||||||
|
TestResults/
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"scopedRegistries": [
|
||||||
|
{
|
||||||
|
"name": "ShrinkSDK",
|
||||||
|
"url": "https://git.crash.work/api/packages/ShrinkSDK/npm/",
|
||||||
|
"scopes": [
|
||||||
|
"com.cneicy"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.test-framework": "1.1.33",
|
||||||
|
"com.cneicy.shrink-app-starter-basic": "file:../../.."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
m_EditorVersion: 2022.3.62f3
|
||||||
|
m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f6be676789111fd4a9afd67b011a12c9
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "ShrinkApp.Starter.Basic.Editor",
|
||||||
|
"rootNamespace": "ShrinkApp.Starter.Basic.Editor",
|
||||||
|
"references": [
|
||||||
|
"ShrinkApp.Starter.Basic.Runtime",
|
||||||
|
"ShrinkApp.Core.Runtime",
|
||||||
|
"ShrinkApp.Core.Editor",
|
||||||
|
"ShrinkContext.AppAdapter.Runtime",
|
||||||
|
"ShrinkDataSaver.Runtime",
|
||||||
|
"ShrinkDataSaver.Integration.App",
|
||||||
|
"UnityEngine.UI"
|
||||||
|
],
|
||||||
|
"includePlatforms": [
|
||||||
|
"Editor"
|
||||||
|
],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aa988409913004043b6cbe3d19de32bd
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
using ShrinkApp.Editor;
|
||||||
|
using ShrinkContext.AppAdapter;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace ShrinkApp.Starter.Basic.Editor
|
||||||
|
{
|
||||||
|
public static class ShrinkAppBasicStarterGenerator
|
||||||
|
{
|
||||||
|
private const string MenuPath = "ShrinkApp/Starter/生成 Basic Entry 场景";
|
||||||
|
private const string CompositionMenuPath = "ShrinkApp/Starter/创建默认 Cordis Composition";
|
||||||
|
private const string CompositionPath = "Assets/Resources/ShrinkAppComposition.asset";
|
||||||
|
|
||||||
|
[MenuItem(MenuPath)]
|
||||||
|
public static void Generate()
|
||||||
|
{
|
||||||
|
EnsureAssets();
|
||||||
|
BuildScene();
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Debug.Log("[ShrinkApp.Starter.Basic] 已生成 Basic Entry 场景与配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem(CompositionMenuPath)]
|
||||||
|
public static void CreateDefaultCompositionAsset()
|
||||||
|
{
|
||||||
|
EnsureResourcesDirectory();
|
||||||
|
var existing = AssetDatabase.LoadAssetAtPath<ShrinkAppCompositionProfile>(CompositionPath);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
Selection.activeObject = existing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复脚本/asmdef 变更期间可能留下的无主 .asset,而不是在同一路径叠加创建。
|
||||||
|
if (!string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(CompositionPath)))
|
||||||
|
AssetDatabase.DeleteAsset(CompositionPath);
|
||||||
|
|
||||||
|
var composition = ScriptableObject.CreateInstance<ShrinkAppCompositionProfile>();
|
||||||
|
composition.SetDocument(ShrinkAppBasicContextComposition.CreateDefaultDocument());
|
||||||
|
AssetDatabase.CreateAsset(composition, CompositionPath);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Selection.activeObject = composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureAssets()
|
||||||
|
{
|
||||||
|
ShrinkAppEditorMenu.CreateSettingsAsset();
|
||||||
|
|
||||||
|
EnsureResourcesDirectory();
|
||||||
|
|
||||||
|
const string dataSaverSettingsPath = "Assets/Resources/ShrinkDataSaverSettings.asset";
|
||||||
|
if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(dataSaverSettingsPath) == null)
|
||||||
|
{
|
||||||
|
var asset = ScriptableObject.CreateInstance("ShrinkDataSaverSettings");
|
||||||
|
if (asset == null)
|
||||||
|
throw new System.InvalidOperationException(
|
||||||
|
"Cannot create ShrinkDataSaverSettings. Ensure ShrinkDataSaver.Runtime is imported and compiled.");
|
||||||
|
|
||||||
|
AssetDatabase.CreateAsset(asset, dataSaverSettingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string starterSettingsPath = "Assets/Resources/ShrinkAppBasicStarterSettings.asset";
|
||||||
|
if (AssetDatabase.LoadAssetAtPath<ShrinkAppBasicStarterSettings>(starterSettingsPath) == null)
|
||||||
|
{
|
||||||
|
var starterAsset = ScriptableObject.CreateInstance<ShrinkAppBasicStarterSettings>();
|
||||||
|
starterAsset.autoBindLoopbackTransport = true;
|
||||||
|
starterAsset.defaultCommandText = "starter status";
|
||||||
|
starterAsset.defaultLoopbackSessionId = 1;
|
||||||
|
starterAsset.logCommandOutputToConsole = true;
|
||||||
|
starterAsset.maxCommandOutputLines = 12;
|
||||||
|
AssetDatabase.CreateAsset(starterAsset, starterSettingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDefaultCompositionAsset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureResourcesDirectory()
|
||||||
|
{
|
||||||
|
const string resourcesDir = "Assets/Resources";
|
||||||
|
if (!AssetDatabase.IsValidFolder(resourcesDir))
|
||||||
|
AssetDatabase.CreateFolder("Assets", "Resources");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildScene()
|
||||||
|
{
|
||||||
|
const string scenesDir = "Assets/Scenes";
|
||||||
|
if (!AssetDatabase.IsValidFolder(scenesDir))
|
||||||
|
AssetDatabase.CreateFolder("Assets", "Scenes");
|
||||||
|
|
||||||
|
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
||||||
|
|
||||||
|
new GameObject("Main Camera", typeof(Camera));
|
||||||
|
new GameObject("EventSystem", typeof(UnityEngine.EventSystems.EventSystem),
|
||||||
|
typeof(UnityEngine.EventSystems.StandaloneInputModule));
|
||||||
|
|
||||||
|
var canvasGo = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||||
|
var canvas = canvasGo.GetComponent<Canvas>();
|
||||||
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||||
|
var scaler = canvasGo.GetComponent<CanvasScaler>();
|
||||||
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||||
|
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||||
|
|
||||||
|
var panelGo = new GameObject("Panel", typeof(RectTransform), typeof(Image));
|
||||||
|
panelGo.transform.SetParent(canvasGo.transform, false);
|
||||||
|
var panelRect = panelGo.GetComponent<RectTransform>();
|
||||||
|
panelRect.sizeDelta = new Vector2(980f, 720f);
|
||||||
|
panelRect.anchoredPosition = Vector2.zero;
|
||||||
|
panelGo.GetComponent<Image>().color = new Color(0.12f, 0.15f, 0.2f, 0.92f);
|
||||||
|
|
||||||
|
var statusText = CreateText("StatusText", panelGo.transform, new Vector2(0f, 240f), new Vector2(840f, 120f),
|
||||||
|
"ShrinkApp Basic starter ready.");
|
||||||
|
statusText.alignment = TextAnchor.MiddleCenter;
|
||||||
|
var detailText = CreateText("DetailText", panelGo.transform, new Vector2(0f, 135f), new Vector2(840f, 140f),
|
||||||
|
"app=False, save=False, cmd=False, net=False");
|
||||||
|
detailText.alignment = TextAnchor.UpperLeft;
|
||||||
|
detailText.fontSize = 20;
|
||||||
|
var commandInputField = CreateInputField("CommandInput", panelGo.transform, new Vector2(0f, 20f),
|
||||||
|
new Vector2(520f, 54f), "starter status");
|
||||||
|
var outputText = CreateText("OutputText", panelGo.transform, new Vector2(0f, -250f), new Vector2(840f, 180f),
|
||||||
|
string.Empty);
|
||||||
|
outputText.alignment = TextAnchor.UpperLeft;
|
||||||
|
outputText.fontSize = 20;
|
||||||
|
outputText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||||
|
outputText.verticalOverflow = VerticalWrapMode.Overflow;
|
||||||
|
var saveMetaText = CreateText("SaveMetaText", panelGo.transform, new Vector2(-280f, -430f), new Vector2(240f, 150f),
|
||||||
|
"No save slots found.");
|
||||||
|
saveMetaText.alignment = TextAnchor.UpperLeft;
|
||||||
|
saveMetaText.fontSize = 18;
|
||||||
|
var moduleText = CreateText("ModuleText", panelGo.transform, new Vector2(0f, -430f), new Vector2(240f, 150f),
|
||||||
|
"No started modules.");
|
||||||
|
moduleText.alignment = TextAnchor.UpperLeft;
|
||||||
|
moduleText.fontSize = 18;
|
||||||
|
var serviceText = CreateText("ServiceText", panelGo.transform, new Vector2(280f, -430f), new Vector2(240f, 150f),
|
||||||
|
"No registered services.");
|
||||||
|
serviceText.alignment = TextAnchor.UpperLeft;
|
||||||
|
serviceText.fontSize = 18;
|
||||||
|
var commandListText = CreateText("CommandListText", panelGo.transform, new Vector2(-280f, -600f), new Vector2(240f, 150f),
|
||||||
|
"Command service not ready.");
|
||||||
|
commandListText.alignment = TextAnchor.UpperLeft;
|
||||||
|
commandListText.fontSize = 18;
|
||||||
|
var networkText = CreateText("NetworkText", panelGo.transform, new Vector2(280f, -600f), new Vector2(240f, 150f),
|
||||||
|
"Network service not ready.");
|
||||||
|
networkText.alignment = TextAnchor.UpperLeft;
|
||||||
|
networkText.fontSize = 18;
|
||||||
|
|
||||||
|
var saveButton = CreateButton("SaveButton", panelGo.transform, new Vector2(-250f, -70f), "Save Slot 0");
|
||||||
|
var loadButton = CreateButton("LoadButton", panelGo.transform, new Vector2(0f, -70f), "Load Slot 0");
|
||||||
|
var commandButton = CreateButton("CommandButton", panelGo.transform, new Vector2(250f, -70f), "Run Command");
|
||||||
|
var loopbackButton = CreateButton("LoopbackButton", panelGo.transform, new Vector2(-250f, -165f), "Bind Loopback");
|
||||||
|
var disconnectButton = CreateButton("DisconnectLoopbackButton", panelGo.transform, new Vector2(0f, -165f), "Disconnect Loopback");
|
||||||
|
var refreshButton = CreateButton("RefreshButton", panelGo.transform, new Vector2(250f, -165f), "Refresh Status");
|
||||||
|
var clearOutputButton = CreateButton("ClearOutputButton", panelGo.transform, new Vector2(0f, -345f), "Clear Output");
|
||||||
|
|
||||||
|
var controller = panelGo.AddComponent<ShrinkAppBasicSampleController>();
|
||||||
|
var serializedObject = new SerializedObject(controller);
|
||||||
|
serializedObject.FindProperty("statusText").objectReferenceValue = statusText;
|
||||||
|
serializedObject.FindProperty("detailText").objectReferenceValue = detailText;
|
||||||
|
serializedObject.FindProperty("outputText").objectReferenceValue = outputText;
|
||||||
|
serializedObject.FindProperty("moduleText").objectReferenceValue = moduleText;
|
||||||
|
serializedObject.FindProperty("serviceText").objectReferenceValue = serviceText;
|
||||||
|
serializedObject.FindProperty("saveMetaText").objectReferenceValue = saveMetaText;
|
||||||
|
serializedObject.FindProperty("commandListText").objectReferenceValue = commandListText;
|
||||||
|
serializedObject.FindProperty("networkText").objectReferenceValue = networkText;
|
||||||
|
serializedObject.FindProperty("saveButton").objectReferenceValue = saveButton;
|
||||||
|
serializedObject.FindProperty("loadButton").objectReferenceValue = loadButton;
|
||||||
|
serializedObject.FindProperty("commandButton").objectReferenceValue = commandButton;
|
||||||
|
serializedObject.FindProperty("loopbackButton").objectReferenceValue = loopbackButton;
|
||||||
|
serializedObject.FindProperty("refreshButton").objectReferenceValue = refreshButton;
|
||||||
|
serializedObject.FindProperty("clearOutputButton").objectReferenceValue = clearOutputButton;
|
||||||
|
serializedObject.FindProperty("disconnectLoopbackButton").objectReferenceValue = disconnectButton;
|
||||||
|
serializedObject.FindProperty("commandInputField").objectReferenceValue = commandInputField;
|
||||||
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
EditorSceneManager.SaveScene(scene, "Assets/Scenes/ShrinkAppEntry.unity");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Text CreateText(string name, Transform parent, Vector2 anchoredPosition, Vector2 size, string text)
|
||||||
|
{
|
||||||
|
var go = new GameObject(name, typeof(RectTransform), typeof(Text));
|
||||||
|
go.transform.SetParent(parent, false);
|
||||||
|
var rect = go.GetComponent<RectTransform>();
|
||||||
|
rect.sizeDelta = size;
|
||||||
|
rect.anchoredPosition = anchoredPosition;
|
||||||
|
var label = go.GetComponent<Text>();
|
||||||
|
label.text = text;
|
||||||
|
label.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||||||
|
label.fontSize = 28;
|
||||||
|
label.color = Color.white;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Button CreateButton(string name, Transform parent, Vector2 anchoredPosition, string labelText)
|
||||||
|
{
|
||||||
|
var go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(Button));
|
||||||
|
go.transform.SetParent(parent, false);
|
||||||
|
var rect = go.GetComponent<RectTransform>();
|
||||||
|
rect.sizeDelta = new Vector2(220f, 72f);
|
||||||
|
rect.anchoredPosition = anchoredPosition;
|
||||||
|
go.GetComponent<Image>().color = new Color(0.24f, 0.45f, 0.75f, 1f);
|
||||||
|
|
||||||
|
var label = CreateText("Label", go.transform, Vector2.zero, new Vector2(220f, 72f), labelText);
|
||||||
|
label.alignment = TextAnchor.MiddleCenter;
|
||||||
|
label.resizeTextForBestFit = true;
|
||||||
|
label.resizeTextMinSize = 18;
|
||||||
|
label.resizeTextMaxSize = 28;
|
||||||
|
|
||||||
|
return go.GetComponent<Button>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InputField CreateInputField(string name, Transform parent, Vector2 anchoredPosition, Vector2 size, string defaultText)
|
||||||
|
{
|
||||||
|
var go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(InputField));
|
||||||
|
go.transform.SetParent(parent, false);
|
||||||
|
var rect = go.GetComponent<RectTransform>();
|
||||||
|
rect.sizeDelta = size;
|
||||||
|
rect.anchoredPosition = anchoredPosition;
|
||||||
|
go.GetComponent<Image>().color = new Color(0.08f, 0.1f, 0.14f, 1f);
|
||||||
|
|
||||||
|
var text = CreateText("Text", go.transform, Vector2.zero, size, defaultText);
|
||||||
|
text.alignment = TextAnchor.MiddleLeft;
|
||||||
|
text.fontSize = 24;
|
||||||
|
text.color = Color.white;
|
||||||
|
var textRect = text.GetComponent<RectTransform>();
|
||||||
|
textRect.offsetMin = new Vector2(16f, 8f);
|
||||||
|
textRect.offsetMax = new Vector2(-16f, -8f);
|
||||||
|
|
||||||
|
var placeholder = CreateText("Placeholder", go.transform, Vector2.zero, size, "Enter command...");
|
||||||
|
placeholder.alignment = TextAnchor.MiddleLeft;
|
||||||
|
placeholder.fontSize = 24;
|
||||||
|
placeholder.color = new Color(1f, 1f, 1f, 0.35f);
|
||||||
|
var placeholderRect = placeholder.GetComponent<RectTransform>();
|
||||||
|
placeholderRect.offsetMin = new Vector2(16f, 8f);
|
||||||
|
placeholderRect.offsetMax = new Vector2(-16f, -8f);
|
||||||
|
|
||||||
|
var inputField = go.GetComponent<InputField>();
|
||||||
|
inputField.textComponent = text;
|
||||||
|
inputField.placeholder = placeholder;
|
||||||
|
inputField.text = defaultText;
|
||||||
|
return inputField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 02316ba70f6e08249b09f5b7ff63eb93
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# ShrinkApp.Starter.Basic
|
||||||
|
|
||||||
|
`ShrinkApp.Starter.Basic` 是 `ShrinkApp` 的最小起盘 Starter。目标不是提供完整游戏模板,而是让项目在导入最小基础包后,能快速得到一个可运行的入口场景、基础配置和最小示例流程。
|
||||||
|
|
||||||
|
在 `ShrinkAppSettings.hostingMode = ContextLoader` 时,Starter 组合根会把 Command、DataSaver、Network 三个已发现模块替换为原生 `IShrinkComponent`,并把兼容用的服务门面作为可逆效应挂回 `ShrinkApp.Services`。因此 Starter 调试台仍可使用原来的服务 API,同时模块 disable/enable 由 `ShrinkContextLoader` 增量协调。
|
||||||
|
|
||||||
|
## 当前能力
|
||||||
|
|
||||||
|
- 生成 `ShrinkAppSettings.asset`
|
||||||
|
- 生成或补齐 `ShrinkDataSaverSettings.asset`
|
||||||
|
- 生成 `ShrinkAppBasicStarterSettings.asset`
|
||||||
|
- 生成 `ShrinkAppComposition.asset`(7 个默认 Cordis 条目;组件工厂仍由代码组合根提供)
|
||||||
|
- 生成 `Assets/Scenes/ShrinkAppEntry.unity`
|
||||||
|
- 生成最小示例 UI 与 `ShrinkAppBasicSampleController`
|
||||||
|
- 演示 `ShrinkApp + ShrinkDataSaver + Command + Network` 的最小闭环
|
||||||
|
|
||||||
|
可以单独执行 `ShrinkApp/Starter/创建默认 Cordis Composition` 创建或修复组合资产,不需要重建场景。
|
||||||
|
|
||||||
|
当前示例场景内置的最小入口已经扩成一个小型调试台,包括:
|
||||||
|
|
||||||
|
- `Save Slot 0`
|
||||||
|
- `Load Slot 0`
|
||||||
|
- `Run starter status`
|
||||||
|
- `Bind Loopback`
|
||||||
|
- `Disconnect Loopback`
|
||||||
|
- `Refresh Status`
|
||||||
|
- `Clear Output`
|
||||||
|
- 命令输入框
|
||||||
|
- 输出日志区
|
||||||
|
- 模块/服务/存档/命令/网络五块只读状态面板
|
||||||
|
|
||||||
|
当前五块只读状态面板分别展示:
|
||||||
|
|
||||||
|
- 已启动模块列表
|
||||||
|
- 已注册服务类型列表
|
||||||
|
- 存档槽位元数据概览
|
||||||
|
- 当前已注册命令列表
|
||||||
|
- 网络诊断摘要与 session 概览
|
||||||
|
|
||||||
|
同时会生成一份 Starter 自己的配置资产,用于控制:
|
||||||
|
|
||||||
|
- 是否在启动时自动绑定 loopback transport
|
||||||
|
- 默认命令文本
|
||||||
|
- 默认 loopback session id
|
||||||
|
- 是否把命令输出打印到控制台
|
||||||
|
- 启动时是否自动执行默认命令
|
||||||
|
- 命令输出区最多保留多少行
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6a8145b76d3e3b445b3768d897fe21c0
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dfffe5a1b320812469b0c0f7a81e790b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "ShrinkApp.Starter.Basic.Runtime",
|
||||||
|
"rootNamespace": "ShrinkApp.Starter.Basic",
|
||||||
|
"references": [
|
||||||
|
"ShrinkApp.Core.Runtime",
|
||||||
|
"ShrinkContext.AppAdapter.Runtime",
|
||||||
|
"ShrinkContext.Core.Runtime",
|
||||||
|
"ShrinkCommand.Runtime",
|
||||||
|
"ShrinkCommand.Integration.App",
|
||||||
|
"ShrinkDataSaver.Integration.App",
|
||||||
|
"ShrinkDataSaver.Runtime",
|
||||||
|
"ShrinkNetwork.Runtime",
|
||||||
|
"ShrinkNetwork.Integration.App",
|
||||||
|
"ShrinkCommand.Integration.EventBus",
|
||||||
|
"ShrinkDataSaver.Integration.EventBus",
|
||||||
|
"ShrinkNetwork.Integration.EventBus",
|
||||||
|
"ShrinkCommand.Integration.Network",
|
||||||
|
"UnityEngine.UI",
|
||||||
|
"UniTask"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 10b7cf71574bd904594339a0824e028b
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using ShrinkCommand.Integration.App;
|
||||||
|
using ShrinkCommand.Integration;
|
||||||
|
using ShrinkContext.AppAdapter;
|
||||||
|
using ShrinkDataSaver.Integration.App;
|
||||||
|
using ShrinkDataSaver.Integration;
|
||||||
|
using ShrinkNetwork.Integration.App;
|
||||||
|
using ShrinkNetwork.Integration;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ShrinkApp.Starter.Basic
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Basic Starter 的组合根:ContextLoader 模式下默认使用原生 Cordis 组件,
|
||||||
|
/// 不再把三个功能模块交给 IShrinkAppModuleInstaller 包装器执行。
|
||||||
|
/// </summary>
|
||||||
|
public static class ShrinkAppBasicContextComposition
|
||||||
|
{
|
||||||
|
private static readonly string[] DefaultModuleIdValues =
|
||||||
|
{
|
||||||
|
"shrink.network",
|
||||||
|
"shrink.command",
|
||||||
|
"shrink.datasaver",
|
||||||
|
"shrink.integration.command-network",
|
||||||
|
"shrink.integration.command-eventbus",
|
||||||
|
"shrink.integration.datasaver-eventbus",
|
||||||
|
"shrink.integration.network-eventbus"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> DefaultModuleIds => DefaultModuleIdValues;
|
||||||
|
|
||||||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||||
|
private static void RegisterDefaultComposition()
|
||||||
|
{
|
||||||
|
ShrinkAppLoaderBootstrapper.DefaultComposition = Configure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Configure(ShrinkAppLoaderHost host)
|
||||||
|
{
|
||||||
|
if (host == null)
|
||||||
|
throw new System.ArgumentNullException(nameof(host));
|
||||||
|
|
||||||
|
RegisterOrReplace(host, "shrink.network", static () => new ShrinkNetworkAppComponent());
|
||||||
|
RegisterOrReplace(host, "shrink.command", static () => new ShrinkCommandAppComponent());
|
||||||
|
RegisterOrReplace(host, "shrink.datasaver", static () => new ShrinkDataSaverAppComponent());
|
||||||
|
host.AddModuleComponent("shrink.integration.command-network",
|
||||||
|
static () => new ShrinkCommandNetworkIntegrationComponent());
|
||||||
|
host.AddModuleComponent("shrink.integration.command-eventbus",
|
||||||
|
static () => new ShrinkCommandEventBusComponent());
|
||||||
|
host.AddModuleComponent("shrink.integration.datasaver-eventbus",
|
||||||
|
static () => new ShrinkDataSaverEventBusComponent());
|
||||||
|
host.AddModuleComponent("shrink.integration.network-eventbus",
|
||||||
|
static () => new ShrinkNetworkEventBusComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterOrReplace(
|
||||||
|
ShrinkAppLoaderHost host,
|
||||||
|
string moduleId,
|
||||||
|
System.Func<ShrinkContext.IShrinkComponent> componentFactory)
|
||||||
|
{
|
||||||
|
if (host.ModuleIds.Any(id => string.Equals(id, moduleId, System.StringComparison.OrdinalIgnoreCase)))
|
||||||
|
host.OverrideModuleComponent(moduleId, componentFactory);
|
||||||
|
else
|
||||||
|
host.AddModuleComponent(moduleId, componentFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShrinkAppCompositionDocument CreateDefaultDocument()
|
||||||
|
{
|
||||||
|
var entries = new List<ShrinkAppCompositionEntry>(DefaultModuleIdValues.Length);
|
||||||
|
foreach (var moduleId in DefaultModuleIdValues)
|
||||||
|
entries.Add(new ShrinkAppCompositionEntry(moduleId));
|
||||||
|
return new ShrinkAppCompositionDocument(includeUnlistedEntries: false, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5b07e8ba4e0ea394d929c688f6f4fcc4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using ShrinkCommand;
|
||||||
|
using ShrinkCommand.Integration.App;
|
||||||
|
using ShrinkContext.AppAdapter;
|
||||||
|
using ShrinkNetwork;
|
||||||
|
using ShrinkNetwork.Integration.App;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ShrinkApp.Starter.Basic
|
||||||
|
{
|
||||||
|
public sealed class ShrinkAppBasicCommandSource : IShrinkCommandSource
|
||||||
|
{
|
||||||
|
private readonly Action<string>? _sink;
|
||||||
|
private readonly bool _logToConsole;
|
||||||
|
|
||||||
|
public ShrinkAppBasicCommandSource(Action<string>? sink = null, bool logToConsole = true)
|
||||||
|
{
|
||||||
|
_sink = sink;
|
||||||
|
_logToConsole = logToConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SourceId => "starter.basic.console";
|
||||||
|
public string DisplayName => "StarterBasicConsole";
|
||||||
|
public bool IsConsole => true;
|
||||||
|
|
||||||
|
public bool HasPermission(string permission) => true;
|
||||||
|
|
||||||
|
public UniTask WriteLineAsync(string message, System.Threading.CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_sink?.Invoke(message);
|
||||||
|
if (_logToConsole)
|
||||||
|
Debug.Log("[ShrinkApp.Basic.Command] " + message);
|
||||||
|
return UniTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ShrinkCommandSubscriber]
|
||||||
|
public static class ShrinkAppBasicDemoCommands
|
||||||
|
{
|
||||||
|
[ShrinkCommand("starter status", Description = "显示 Starter.Basic 当前状态")]
|
||||||
|
private static string StarterStatus()
|
||||||
|
{
|
||||||
|
var appRunning = ShrinkApp.IsRunning;
|
||||||
|
var loadedSlot = ShrinkDataSaver.ShrinkSave.LoadedSlot;
|
||||||
|
return $"appRunning={appRunning}, loadedSlot={loadedSlot}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ShrinkAppBasicDemoRuntime
|
||||||
|
{
|
||||||
|
private const string SettingsResourceName = "ShrinkAppBasicStarterSettings";
|
||||||
|
private static ShrinkLoopbackTransport? _loopbackClientTransport;
|
||||||
|
private static ShrinkLoopbackTransport? _loopbackServerTransport;
|
||||||
|
private static long _activeLoopbackSessionId = -1;
|
||||||
|
|
||||||
|
public static ShrinkAppBasicStarterSettings GetOrCreateSettings()
|
||||||
|
{
|
||||||
|
var asset = Resources.Load<ShrinkAppBasicStarterSettings>(SettingsResourceName);
|
||||||
|
if (asset != null)
|
||||||
|
return asset;
|
||||||
|
|
||||||
|
var fallback = ScriptableObject.CreateInstance<ShrinkAppBasicStarterSettings>();
|
||||||
|
fallback.defaultCommandText = "starter status";
|
||||||
|
fallback.defaultLoopbackSessionId = 1;
|
||||||
|
fallback.logCommandOutputToConsole = true;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildStatusSummary()
|
||||||
|
{
|
||||||
|
var commandReady = ShrinkApp.Context?.Services.TryGet<ShrinkCommandAppService>(out _) == true;
|
||||||
|
var networkReady = ShrinkApp.Context?.Services.TryGet<ShrinkNetworkAppService>(out _) == true;
|
||||||
|
var dataSaverReady = ShrinkApp.Context?.Services.TryGet<ShrinkDataSaver.Integration.App.ShrinkDataSaverService>(out _) == true;
|
||||||
|
var appRunning = ShrinkApp.IsRunning || ShrinkAppLoaderBootstrapper.Instance?.Host.IsRunning == true;
|
||||||
|
return $"app={appRunning}, save={dataSaverReady}, cmd={commandReady}, net={networkReady}, loopbackSession={_activeLoopbackSessionId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildDetailedStatus()
|
||||||
|
{
|
||||||
|
var settings = GetOrCreateSettings();
|
||||||
|
var recentSlot = ShrinkDataSaver.ShrinkSave.GetRecentSlotIndex();
|
||||||
|
var commandService = ShrinkApp.Context?.Services.TryGet<ShrinkCommandAppService>(out var command) == true ? command : null;
|
||||||
|
var networkService = ShrinkApp.Context?.Services.TryGet<ShrinkNetworkAppService>(out var network) == true ? network : null;
|
||||||
|
var diagnostics = networkService?.GetDiagnosticsSnapshot();
|
||||||
|
var moduleCount = ShrinkAppLoaderBootstrapper.Instance?.Host.ActiveModuleIds.Count
|
||||||
|
?? ShrinkApp.Host?.StartedModules.Count
|
||||||
|
?? 0;
|
||||||
|
var serviceCount = ShrinkApp.Context?.Services.GetRegisteredServiceTypeNames().Count ?? 0;
|
||||||
|
return
|
||||||
|
$"defaultCommand={settings.defaultCommandText}\n" +
|
||||||
|
$"loopbackAutoBind={settings.autoBindLoopbackTransport}, sessionId={settings.defaultLoopbackSessionId}\n" +
|
||||||
|
$"recentSlot={recentSlot}, loadedSlot={ShrinkDataSaver.ShrinkSave.LoadedSlot}\n" +
|
||||||
|
$"modules={moduleCount}, services={serviceCount}, commandCount={commandService?.Commands.Count ?? 0}, currentSessions={diagnostics?.CurrentSessions ?? 0}\n" +
|
||||||
|
$"{BuildStatusSummary()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildModuleSummary()
|
||||||
|
{
|
||||||
|
var loaderHost = ShrinkAppLoaderBootstrapper.Instance?.Host;
|
||||||
|
if (loaderHost != null && loaderHost.ActiveModuleIds.Count > 0)
|
||||||
|
return string.Join("\n", loaderHost.ActiveModuleIds);
|
||||||
|
|
||||||
|
if (ShrinkApp.Host?.StartedModules == null || ShrinkApp.Host.StartedModules.Count == 0)
|
||||||
|
return "No started modules.";
|
||||||
|
|
||||||
|
return string.Join("\n", ShrinkApp.Host.StartedModules);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildServiceSummary()
|
||||||
|
{
|
||||||
|
if (ShrinkApp.Context == null)
|
||||||
|
return "No registered services.";
|
||||||
|
|
||||||
|
return string.Join("\n", ShrinkApp.Context.Services.GetRegisteredServiceTypeNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildSaveMetaSummary(ShrinkDataSaver.SaveMeta[]? metas)
|
||||||
|
{
|
||||||
|
if (metas == null || metas.Length == 0)
|
||||||
|
return "No save slots found.";
|
||||||
|
|
||||||
|
return string.Join("\n", metas
|
||||||
|
.OrderBy(meta => meta.SlotIndex)
|
||||||
|
.Select(meta =>
|
||||||
|
$"slot={meta.SlotIndex}, name={meta.SlotName}, playtime={meta.PlaytimeSeconds:F0}s, modified={meta.LastModifiedTime:g}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildCommandListSummary()
|
||||||
|
{
|
||||||
|
if (ShrinkApp.Context?.Services.TryGet<ShrinkCommandAppService>(out var commandService) != true || commandService == null)
|
||||||
|
return "Command service not ready.";
|
||||||
|
|
||||||
|
return string.Join("\n", commandService.Commands
|
||||||
|
.OrderBy(command => command.Path, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Take(10)
|
||||||
|
.Select(command => command.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildNetworkSummary()
|
||||||
|
{
|
||||||
|
if (ShrinkApp.Context?.Services.TryGet<ShrinkNetworkAppService>(out var networkService) != true || networkService == null)
|
||||||
|
return "Network service not ready.";
|
||||||
|
|
||||||
|
var diagnostics = networkService.GetDiagnosticsSnapshot();
|
||||||
|
var sessions = networkService.GetSessionSummaries();
|
||||||
|
return
|
||||||
|
$"sessions={diagnostics.CurrentSessions}, connected={diagnostics.SessionsConnected}, disconnected={diagnostics.SessionsDisconnected}\n" +
|
||||||
|
$"packetsSent={diagnostics.PacketsSent}, packetsReceived={diagnostics.PacketsReceived}\n" +
|
||||||
|
$"rpcStarted={diagnostics.RpcStarted}, rpcCompleted={diagnostics.RpcCompleted}\n" +
|
||||||
|
string.Join("\n", sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async UniTask<string> ExecuteCommandAsync(string? commandText, Action<string>? outputSink = null)
|
||||||
|
{
|
||||||
|
var command = ShrinkApp.Services.GetRequired<ShrinkCommandAppService>();
|
||||||
|
var settings = GetOrCreateSettings();
|
||||||
|
var source = new ShrinkAppBasicCommandSource(outputSink, settings.logCommandOutputToConsole);
|
||||||
|
var effectiveCommand = string.IsNullOrWhiteSpace(commandText)
|
||||||
|
? settings.defaultCommandText
|
||||||
|
: commandText.Trim();
|
||||||
|
var result = await command.ExecuteAsync(source, effectiveCommand);
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.Message))
|
||||||
|
outputSink?.Invoke(result.Message);
|
||||||
|
return result.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string EnsureLoopbackReady()
|
||||||
|
{
|
||||||
|
var network = ShrinkApp.Services.GetRequired<ShrinkNetworkAppService>().Service;
|
||||||
|
var settings = GetOrCreateSettings();
|
||||||
|
if (_loopbackClientTransport == null || _loopbackServerTransport == null)
|
||||||
|
{
|
||||||
|
_loopbackClientTransport = new ShrinkLoopbackTransport();
|
||||||
|
_loopbackServerTransport = new ShrinkLoopbackTransport();
|
||||||
|
_loopbackClientTransport.LinkPeer(_loopbackServerTransport);
|
||||||
|
}
|
||||||
|
|
||||||
|
network.BindTransport(_loopbackClientTransport);
|
||||||
|
if (_activeLoopbackSessionId != settings.defaultLoopbackSessionId)
|
||||||
|
{
|
||||||
|
if (_activeLoopbackSessionId >= 0)
|
||||||
|
_loopbackClientTransport.CloseSession(_activeLoopbackSessionId);
|
||||||
|
|
||||||
|
_activeLoopbackSessionId = settings.defaultLoopbackSessionId;
|
||||||
|
_loopbackClientTransport.OpenSession(_activeLoopbackSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"loopbackBound={network.Sessions.Count}, session={_activeLoopbackSessionId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AutoBindLoopbackIfEnabled()
|
||||||
|
{
|
||||||
|
var settings = GetOrCreateSettings();
|
||||||
|
if (!settings.autoBindLoopbackTransport)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return EnsureLoopbackReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DisconnectLoopback()
|
||||||
|
{
|
||||||
|
if (_loopbackClientTransport == null || _activeLoopbackSessionId < 0)
|
||||||
|
return "loopbackNotBound";
|
||||||
|
|
||||||
|
_loopbackClientTransport.CloseSession(_activeLoopbackSessionId);
|
||||||
|
_activeLoopbackSessionId = -1;
|
||||||
|
return "loopbackDisconnected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 21ca7aa1ab7f9c64e9b4d050458df3d4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using ShrinkDataSaver;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace ShrinkApp.Starter.Basic
|
||||||
|
{
|
||||||
|
public sealed class ShrinkAppBasicSampleController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Text? statusText;
|
||||||
|
[SerializeField] private Text? detailText;
|
||||||
|
[SerializeField] private Text? outputText;
|
||||||
|
[SerializeField] private Text? moduleText;
|
||||||
|
[SerializeField] private Text? serviceText;
|
||||||
|
[SerializeField] private Text? saveMetaText;
|
||||||
|
[SerializeField] private Text? commandListText;
|
||||||
|
[SerializeField] private Text? networkText;
|
||||||
|
[SerializeField] private Button? saveButton;
|
||||||
|
[SerializeField] private Button? loadButton;
|
||||||
|
[SerializeField] private Button? commandButton;
|
||||||
|
[SerializeField] private Button? loopbackButton;
|
||||||
|
[SerializeField] private Button? refreshButton;
|
||||||
|
[SerializeField] private Button? clearOutputButton;
|
||||||
|
[SerializeField] private Button? disconnectLoopbackButton;
|
||||||
|
[SerializeField] private InputField? commandInputField;
|
||||||
|
|
||||||
|
private int _counter;
|
||||||
|
private readonly List<string> _outputLines = new();
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (saveButton != null)
|
||||||
|
saveButton.onClick.AddListener(() => SaveAsync().Forget());
|
||||||
|
if (loadButton != null)
|
||||||
|
loadButton.onClick.AddListener(() => LoadAsync().Forget());
|
||||||
|
if (commandButton != null)
|
||||||
|
commandButton.onClick.AddListener(() => RunCommandAsync().Forget());
|
||||||
|
if (loopbackButton != null)
|
||||||
|
loopbackButton.onClick.AddListener(BindLoopback);
|
||||||
|
if (refreshButton != null)
|
||||||
|
refreshButton.onClick.AddListener(RefreshPanels);
|
||||||
|
if (clearOutputButton != null)
|
||||||
|
clearOutputButton.onClick.AddListener(ClearOutput);
|
||||||
|
if (disconnectLoopbackButton != null)
|
||||||
|
disconnectLoopbackButton.onClick.AddListener(DisconnectLoopback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
var settings = ShrinkAppBasicDemoRuntime.GetOrCreateSettings();
|
||||||
|
if (commandInputField != null)
|
||||||
|
commandInputField.text = settings.defaultCommandText;
|
||||||
|
|
||||||
|
var autoLoopbackMessage = ShrinkAppBasicDemoRuntime.AutoBindLoopbackIfEnabled();
|
||||||
|
RefreshStatus("ShrinkApp Basic starter ready.");
|
||||||
|
if (!string.IsNullOrWhiteSpace(autoLoopbackMessage))
|
||||||
|
AppendOutput(autoLoopbackMessage);
|
||||||
|
RefreshPanels();
|
||||||
|
|
||||||
|
if (settings.autoRunDefaultCommandOnStart)
|
||||||
|
RunCommandAsync().Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid SaveAsync()
|
||||||
|
{
|
||||||
|
_counter++;
|
||||||
|
ShrinkSettings.Set("ShrinkApp.Basic.Counter", _counter);
|
||||||
|
await ShrinkSave.SaveSlotAsync(0);
|
||||||
|
RefreshStatus($"Saved counter={_counter}, recentSlot={ShrinkSave.GetRecentSlotIndex()}");
|
||||||
|
AppendOutput("save: slot 0");
|
||||||
|
RefreshPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid LoadAsync()
|
||||||
|
{
|
||||||
|
if (await ShrinkSave.SlotExistsAsync(0))
|
||||||
|
await ShrinkSave.LoadSlotAsync(0);
|
||||||
|
|
||||||
|
_counter = ShrinkSettings.Get("ShrinkApp.Basic.Counter", 0);
|
||||||
|
RefreshStatus($"Loaded counter={_counter}, loadedSlot={ShrinkSave.LoadedSlot}");
|
||||||
|
AppendOutput("load: slot 0");
|
||||||
|
RefreshPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid RunCommandAsync()
|
||||||
|
{
|
||||||
|
var message = await ShrinkAppBasicDemoRuntime.ExecuteCommandAsync(commandInputField?.text, AppendOutput);
|
||||||
|
RefreshStatus(message);
|
||||||
|
RefreshPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BindLoopback()
|
||||||
|
{
|
||||||
|
var message = ShrinkAppBasicDemoRuntime.EnsureLoopbackReady();
|
||||||
|
AppendOutput(message);
|
||||||
|
RefreshStatus(message);
|
||||||
|
RefreshPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisconnectLoopback()
|
||||||
|
{
|
||||||
|
var message = ShrinkAppBasicDemoRuntime.DisconnectLoopback();
|
||||||
|
AppendOutput(message);
|
||||||
|
RefreshStatus(message);
|
||||||
|
RefreshPanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshPanels()
|
||||||
|
{
|
||||||
|
RefreshDetail(ShrinkAppBasicDemoRuntime.BuildDetailedStatus());
|
||||||
|
RefreshInspectorText().Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshStatus(string text)
|
||||||
|
{
|
||||||
|
if (statusText != null)
|
||||||
|
statusText.text = text;
|
||||||
|
|
||||||
|
Debug.Log("[ShrinkApp.Starter.Basic] " + text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshDetail(string text)
|
||||||
|
{
|
||||||
|
if (detailText != null)
|
||||||
|
detailText.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendOutput(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var settings = ShrinkAppBasicDemoRuntime.GetOrCreateSettings();
|
||||||
|
_outputLines.Add(text.Trim());
|
||||||
|
while (_outputLines.Count > settings.maxCommandOutputLines)
|
||||||
|
_outputLines.RemoveAt(0);
|
||||||
|
|
||||||
|
if (outputText != null)
|
||||||
|
outputText.text = string.Join("\n", _outputLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearOutput()
|
||||||
|
{
|
||||||
|
_outputLines.Clear();
|
||||||
|
if (outputText != null)
|
||||||
|
outputText.text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid RefreshInspectorText()
|
||||||
|
{
|
||||||
|
if (saveMetaText != null)
|
||||||
|
{
|
||||||
|
var metas = await ShrinkSave.GetAllMetaAsync();
|
||||||
|
saveMetaText.text = ShrinkAppBasicDemoRuntime.BuildSaveMetaSummary(metas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleText != null)
|
||||||
|
moduleText.text = ShrinkAppBasicDemoRuntime.BuildModuleSummary();
|
||||||
|
|
||||||
|
if (serviceText != null)
|
||||||
|
serviceText.text = ShrinkAppBasicDemoRuntime.BuildServiceSummary();
|
||||||
|
|
||||||
|
if (commandListText != null)
|
||||||
|
commandListText.text = ShrinkAppBasicDemoRuntime.BuildCommandListSummary();
|
||||||
|
|
||||||
|
if (networkText != null)
|
||||||
|
networkText.text = ShrinkAppBasicDemoRuntime.BuildNetworkSummary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2bc64b7d684d71f469b2335a8c4269ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ShrinkApp.Starter.Basic
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "ShrinkAppBasicStarterSettings", menuName = "ShrinkApp/Starter/Basic Settings")]
|
||||||
|
public class ShrinkAppBasicStarterSettings : ScriptableObject
|
||||||
|
{
|
||||||
|
public bool autoBindLoopbackTransport;
|
||||||
|
public long defaultLoopbackSessionId = 1;
|
||||||
|
public string defaultCommandText = "starter status";
|
||||||
|
public bool logCommandOutputToConsole = true;
|
||||||
|
public bool autoRunDefaultCommandOnStart;
|
||||||
|
public int maxCommandOutputLines = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a4a0f6f7946adc45a3437ca04698c65
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "com.cneicy.shrink-app-starter-basic",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"displayName": "ShrinkApp Starter Basic",
|
||||||
|
"description": "ShrinkApp 最小起盘 Starter,提供向导、入口场景和 DataSaver 示例。",
|
||||||
|
"unity": "2022.3",
|
||||||
|
"dependencies": {
|
||||||
|
"com.cneicy.shrink-app-core": "0.1.1",
|
||||||
|
"com.cneicy.shrink-context-core": "0.1.0",
|
||||||
|
"com.cneicy.shrink-context-app-adapter": "0.1.0",
|
||||||
|
"com.cneicy.shrink-command": "0.2.0",
|
||||||
|
"com.cneicy.shrink-command-integration-app": "0.1.0",
|
||||||
|
"com.cneicy.shrink-datasaver": "2.2.0",
|
||||||
|
"com.cneicy.shrink-datasaver-integration-app": "0.1.0",
|
||||||
|
"com.cneicy.shrink-network": "0.2.0",
|
||||||
|
"com.cneicy.shrink-network-integration-app": "0.1.0",
|
||||||
|
"com.cneicy.shrink-command-integration-eventbus": "0.1.1",
|
||||||
|
"com.cneicy.shrink-datasaver-integration-eventbus": "2.1.0",
|
||||||
|
"com.cneicy.shrink-network-integration-eventbus": "0.1.1",
|
||||||
|
"com.cneicy.shrink-command-integration-network": "0.1.0",
|
||||||
|
"com.unity.ugui": "1.0.0",
|
||||||
|
"com.cysharp.unitask": "2.5.10"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"starter",
|
||||||
|
"app",
|
||||||
|
"bootstrap",
|
||||||
|
"sample"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "cneicy",
|
||||||
|
"url": "https://git.crash.work/ShrinkSDK"
|
||||||
|
},
|
||||||
|
"documentationUrl": "https://git.crash.work/ShrinkSDK/ShrinkApp.Starter.Basic",
|
||||||
|
"changelogUrl": "https://git.crash.work/ShrinkSDK/ShrinkApp.Starter.Basic/src/branch/main/CHANGELOG.md"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 715b4b70d23970947be94997f77fc319
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user