Skip to content

Commit d9508df

Browse files
refactor(core): extract MessageBox to separate UI package
Make core lightweight by extracting MessageBox with an abstraction layer: - Add Prompt abstraction in core for user dialogs (delegate-based) - Create new jengine.ui package for UI utilities - Move MessageBox from core to ui package (namespace: JEngine.UI) - Update Bootstrap to use Prompt.ShowDialogAsync instead of MessageBox - Add PromptInitializer.cs for easy UI integration Package structure after changes: - jengine.core: Independent hot update only, with Prompt abstraction - jengine.ui: Optional UI utilities (MessageBox) Users can customize prompt behavior by: 1. Using the default MessageBox via PromptInitializer 2. Implementing custom dialog providers This keeps core non-invasive (无侵入式) for hot updates while allowing UI customization through the optional ui package. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net>
1 parent 17b06ca commit d9508df

26 files changed

Lines changed: 703 additions & 35 deletions

.github/workflows/pr-tests.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
src:
3838
- 'UnityProject/Packages/com.jasonxudeveloper.jengine.core/**'
3939
- 'UnityProject/Packages/com.jasonxudeveloper.jengine.util/**'
40+
- 'UnityProject/Packages/com.jasonxudeveloper.jengine.ui/**'
4041
- 'UnityProject/Assets/Tests/**'
4142
- '.github/workflows/unity-tests.yml'
4243
- '.github/workflows/pr-tests.yml'
@@ -105,7 +106,17 @@ jobs:
105106
files: coverage/**/TestCoverageResults*.xml
106107
flags: util
107108
name: jengine-util
108-
fail_ci_if_error: false
109+
fail_ci_if_error: true
110+
verbose: true
111+
112+
- name: Upload coverage to Codecov (ui package)
113+
uses: codecov/codecov-action@v4
114+
with:
115+
token: ${{ secrets.CODECOV_TOKEN }}
116+
files: coverage/**/TestCoverageResults*.xml
117+
flags: ui
118+
name: jengine-ui
119+
fail_ci_if_error: true
109120
verbose: true
110121

111122
comment-results:

.github/workflows/release.yml

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ on:
2121
description: 'New Util version (e.g., 1.0.1)'
2222
required: false
2323
type: string
24+
release_ui:
25+
description: 'Release JEngine.UI?'
26+
required: true
27+
type: boolean
28+
default: false
29+
ui_version:
30+
description: 'New UI version (e.g., 1.0.0)'
31+
required: false
32+
type: string
2433
manual_changelog:
2534
description: 'Manual changelog entries (optional)'
2635
required: false
@@ -37,6 +46,7 @@ jobs:
3746
outputs:
3847
core_version: ${{ steps.validate.outputs.core_version }}
3948
util_version: ${{ steps.validate.outputs.util_version }}
49+
ui_version: ${{ steps.validate.outputs.ui_version }}
4050
release_tag: ${{ steps.validate.outputs.release_tag }}
4151
create_github_release: ${{ steps.validate.outputs.create_github_release }}
4252

@@ -48,7 +58,7 @@ jobs:
4858
id: validate
4959
run: |
5060
# Check at least one package is selected
51-
if [ "${{ inputs.release_core }}" != "true" ] && [ "${{ inputs.release_util }}" != "true" ]; then
61+
if [ "${{ inputs.release_core }}" != "true" ] && [ "${{ inputs.release_util }}" != "true" ] && [ "${{ inputs.release_ui }}" != "true" ]; then
5262
echo "Error: At least one package must be selected for release"
5363
exit 1
5464
fi
@@ -65,9 +75,11 @@ jobs:
6575
# Get current versions from package.json
6676
CURRENT_CORE_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.core/package.json)
6777
CURRENT_UTIL_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.util/package.json)
78+
CURRENT_UI_VERSION=$(jq -r '.version' UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json)
6879
6980
echo "Current Core version: $CURRENT_CORE_VERSION"
7081
echo "Current Util version: $CURRENT_UTIL_VERSION"
82+
echo "Current UI version: $CURRENT_UI_VERSION"
7183
7284
# Validate Core version if releasing
7385
if [ "${{ inputs.release_core }}" == "true" ]; then
@@ -106,6 +118,24 @@ jobs:
106118
echo "util_version=$CURRENT_UTIL_VERSION" >> $GITHUB_OUTPUT
107119
fi
108120
121+
# Validate UI version if releasing
122+
if [ "${{ inputs.release_ui }}" == "true" ]; then
123+
if [ -z "${{ inputs.ui_version }}" ]; then
124+
echo "Error: UI version is required when releasing UI package"
125+
exit 1
126+
fi
127+
validate_version "${{ inputs.ui_version }}"
128+
129+
if [ "${{ inputs.ui_version }}" == "$CURRENT_UI_VERSION" ]; then
130+
echo "Error: New UI version must be different from current version"
131+
exit 1
132+
fi
133+
134+
echo "ui_version=${{ inputs.ui_version }}" >> $GITHUB_OUTPUT
135+
else
136+
echo "ui_version=$CURRENT_UI_VERSION" >> $GITHUB_OUTPUT
137+
fi
138+
109139
# Release tag always follows Core version
110140
# GitHub releases are only created when Core is released
111141
if [ "${{ inputs.release_core }}" == "true" ]; then
@@ -276,13 +306,36 @@ jobs:
276306
# Build changelog
277307
CHANGELOG=""
278308
279-
# Add package release info (always show both versions for clarity)
280-
if [ "${{ inputs.release_core }}" == "true" ] && [ "${{ inputs.release_util }}" == "true" ]; then
281-
CHANGELOG="${CHANGELOG}**Released**: JEngine.Core v${{ needs.validate.outputs.core_version }}, JEngine.Util v${{ needs.validate.outputs.util_version }}\n\n"
282-
elif [ "${{ inputs.release_core }}" == "true" ]; then
283-
CHANGELOG="${CHANGELOG}**Released**: JEngine.Core v${{ needs.validate.outputs.core_version }} (Util remains v${{ needs.validate.outputs.util_version }})\n\n"
309+
# Add package release info
310+
RELEASED_PACKAGES=""
311+
UNCHANGED_PACKAGES=""
312+
313+
if [ "${{ inputs.release_core }}" == "true" ]; then
314+
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.Core v${{ needs.validate.outputs.core_version }}, "
284315
else
285-
CHANGELOG="${CHANGELOG}**Released**: JEngine.Util v${{ needs.validate.outputs.util_version }} (Core remains v${{ needs.validate.outputs.core_version }})\n\n"
316+
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}Core v${{ needs.validate.outputs.core_version }}, "
317+
fi
318+
319+
if [ "${{ inputs.release_util }}" == "true" ]; then
320+
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.Util v${{ needs.validate.outputs.util_version }}, "
321+
else
322+
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}Util v${{ needs.validate.outputs.util_version }}, "
323+
fi
324+
325+
if [ "${{ inputs.release_ui }}" == "true" ]; then
326+
RELEASED_PACKAGES="${RELEASED_PACKAGES}JEngine.UI v${{ needs.validate.outputs.ui_version }}, "
327+
else
328+
UNCHANGED_PACKAGES="${UNCHANGED_PACKAGES}UI v${{ needs.validate.outputs.ui_version }}, "
329+
fi
330+
331+
# Remove trailing comma and space
332+
RELEASED_PACKAGES=$(echo "$RELEASED_PACKAGES" | sed 's/, $//')
333+
UNCHANGED_PACKAGES=$(echo "$UNCHANGED_PACKAGES" | sed 's/, $//')
334+
335+
if [ -n "$UNCHANGED_PACKAGES" ]; then
336+
CHANGELOG="${CHANGELOG}**Released**: ${RELEASED_PACKAGES} (${UNCHANGED_PACKAGES} unchanged)\n\n"
337+
else
338+
CHANGELOG="${CHANGELOG}**Released**: ${RELEASED_PACKAGES}\n\n"
286339
fi
287340
288341
if [ -n "$BREAKING" ]; then
@@ -341,6 +394,14 @@ jobs:
341394
mv /tmp/package.json UnityProject/Packages/com.jasonxudeveloper.jengine.util/package.json
342395
echo "✅ Updated Util package.json to v${{ needs.validate.outputs.util_version }}"
343396
397+
- name: Update UI package.json
398+
if: inputs.release_ui == true
399+
run: |
400+
jq '.version = "${{ needs.validate.outputs.ui_version }}"' \
401+
UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json > /tmp/package.json
402+
mv /tmp/package.json UnityProject/Packages/com.jasonxudeveloper.jengine.ui/package.json
403+
echo "✅ Updated UI package.json to v${{ needs.validate.outputs.ui_version }}"
404+
344405
# Update README files (only when releasing Core)
345406
- name: Update README.md
346407
if: inputs.release_core == true
@@ -554,13 +615,17 @@ jobs:
554615
echo "✅ **JEngine.Util**: v${{ needs.validate.outputs.util_version }}" >> $GITHUB_STEP_SUMMARY
555616
fi
556617
618+
if [ "${{ inputs.release_ui }}" == "true" ]; then
619+
echo "✅ **JEngine.UI**: v${{ needs.validate.outputs.ui_version }}" >> $GITHUB_STEP_SUMMARY
620+
fi
621+
557622
echo "" >> $GITHUB_STEP_SUMMARY
558623
echo "🏷️ **Git Tag**: ${{ needs.validate.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY
559624
560625
if [ "${{ needs.validate.outputs.create_github_release }}" == "true" ]; then
561626
echo "📋 **GitHub Release**: Will be created" >> $GITHUB_STEP_SUMMARY
562627
else
563-
echo "ℹ️ **GitHub Release**: Not created (Util-only update)" >> $GITHUB_STEP_SUMMARY
628+
echo "ℹ️ **GitHub Release**: Not created (non-Core update)" >> $GITHUB_STEP_SUMMARY
564629
fi
565630
566631
echo "" >> $GITHUB_STEP_SUMMARY
@@ -605,6 +670,7 @@ jobs:
605670
```bash
606671
openupm add com.jasonxudeveloper.jengine.core
607672
openupm add com.jasonxudeveloper.jengine.util
673+
openupm add com.jasonxudeveloper.jengine.ui # Optional: UI utilities
608674
```
609675
610676
## 📖 Documentation
@@ -637,6 +703,10 @@ jobs:
637703
echo "✅ **JEngine.Util**: v${{ needs.validate.outputs.util_version }}" >> $GITHUB_STEP_SUMMARY
638704
fi
639705
706+
if [ "${{ inputs.release_ui }}" == "true" ]; then
707+
echo "✅ **JEngine.UI**: v${{ needs.validate.outputs.ui_version }}" >> $GITHUB_STEP_SUMMARY
708+
fi
709+
640710
echo "" >> $GITHUB_STEP_SUMMARY
641711
echo "**OpenUPM will automatically detect and build the packages within 10-15 minutes.**" >> $GITHUB_STEP_SUMMARY
642712
echo "" >> $GITHUB_STEP_SUMMARY

UnityProject/Assets/HotUpdate/Code/EntryPoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
using Cysharp.Threading.Tasks;
2727
using JEngine.Core;
2828
using JEngine.Core.Encrypt;
29-
using JEngine.Core.Misc;
3029
using JEngine.Core.Update;
30+
using JEngine.UI;
3131
using Obfuz;
3232
using UnityEngine;
3333
using UnityEngine.Scripting;

UnityProject/Assets/HotUpdate/Code/HotUpdate.Code.asmdef

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"GUID:e34a5702dd353724aa315fb8011f08c3",
1212
"GUID:3fe1a3e70da50184f9897101cad7e4f2",
1313
"GUID:6055be8ebefd69e48b49212b09b47b2f",
14-
"GUID:ba02d1bbd77cf4a0c8606d3e5cbbbe79"
14+
"GUID:ba02d1bbd77cf4a0c8606d3e5cbbbe79",
15+
"GUID:5c8e1f4d7a3b9e2c6f0d8a4b7e3c1f9d",
16+
"GUID:5655bcbaa4dec434b86ecd07e2c6f24d"
1517
],
1618
"includePlatforms": [],
1719
"excludePlatforms": [],

UnityProject/Assets/Scripts.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// PromptInitializer.cs
2+
//
3+
// Author:
4+
// JasonXuDeveloper <jason@xgamedev.net>
5+
//
6+
// Copyright (c) 2025 JEngine
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be included in
16+
// all copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
26+
// ============================================================================
27+
// IMPORTANT: This script requires the JEngine.UI package to be installed.
28+
//
29+
// To use this script:
30+
// 1. Add the JEngine.UI package via OpenUPM:
31+
// openupm add com.jasonxudeveloper.jengine.ui
32+
//
33+
// 2. Or add to your manifest.json:
34+
// "com.jasonxudeveloper.jengine.ui": "1.0.0"
35+
//
36+
// If you don't need MessageBox dialogs, you can:
37+
// - Delete this script entirely (Bootstrap will log warnings and continue)
38+
// - Implement your own custom dialog provider by assigning to Prompt.ShowDialogAsync
39+
// ============================================================================
40+
41+
using JEngine.Core;
42+
using JEngine.UI;
43+
using UnityEngine;
44+
45+
/// <summary>
46+
/// Initializes the Prompt system to use MessageBox for dialogs.
47+
/// This runs automatically before any scene loads.
48+
/// </summary>
49+
public static class PromptInitializer
50+
{
51+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
52+
private static void Initialize()
53+
{
54+
// Register MessageBox as the dialog provider for JEngine.Core
55+
Prompt.ShowDialogAsync = MessageBox.Show;
56+
Debug.Log("[JEngine] Prompt system initialized with MessageBox provider.");
57+
}
58+
}

UnityProject/Assets/Scripts/PromptInitializer.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityProject/Packages/com.jasonxudeveloper.jengine.core/Runtime/Bootstrap.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
using System.Threading.Tasks;
3232
using Cysharp.Threading.Tasks;
3333
using JEngine.Core.Encrypt;
34-
using JEngine.Core.Misc;
3534
using JEngine.Core.Update;
3635
using Nino.Core;
3736
using Obfuz;
@@ -203,7 +202,7 @@ private async void Initialize()
203202
}
204203
catch (Exception e)
205204
{
206-
await MessageBox.Show("Error", $"Initialization failed: {e.Message}", no: null);
205+
await Prompt.ShowDialogAsync("Error", $"Initialization failed: {e.Message}", "OK", null);
207206
Application.Quit();
208207
}
209208
}
@@ -235,9 +234,9 @@ private async UniTask InitializeGame()
235234
OnStatusUpdate = status => updateStatusText.text = GetStatusText(status),
236235
OnVersionUpdate = version => versionText.text = $"v{Application.version}.{version}",
237236
OnDownloadPrompt = async (count, size) =>
238-
await MessageBox.Show("Notice",
237+
await Prompt.ShowDialogAsync("Notice",
239238
$"Need to download {count} files, total size {size / 1024f / 1024f:F2}MB. Start download?",
240-
"Download"),
239+
"Download", "Cancel"),
241240
OnDownloadProgress = data =>
242241
{
243242
if (updateStatusText != null)
@@ -272,7 +271,7 @@ await MessageBox.Show("Notice",
272271
if (downloadProgressBar != null)
273272
downloadProgressBar.value = 1f;
274273
},
275-
OnError = async error => await MessageBox.Show("Warning", error.Message, no: null)
274+
OnError = async error => await Prompt.ShowDialogAsync("Warning", error.Message, "OK", null)
276275
};
277276

278277
bool success = await UpdatePackage(package, packageInitCallbacks, encryptionOption);
@@ -328,8 +327,8 @@ await MessageBox.Show("Notice",
328327
},
329328
OnError = async exception =>
330329
{
331-
await MessageBox.Show("Error", $"Scene loading failed: {exception.Message}",
332-
ok: "Retry");
330+
await Prompt.ShowDialogAsync("Error", $"Scene loading failed: {exception.Message}",
331+
"Retry", null);
333332
}
334333
};
335334
downloadProgressBar.gameObject.SetActive(true);
@@ -342,7 +341,7 @@ await MessageBox.Show("Error", $"Scene loading failed: {exception.Message}",
342341
catch (Exception ex)
343342
{
344343
Debug.LogError($"Initialization failed with exception: {ex}");
345-
await MessageBox.Show("Error", $"Exception occurred during initialization: {ex.Message}");
344+
await Prompt.ShowDialogAsync("Error", $"Exception occurred during initialization: {ex.Message}", "OK", "Cancel");
346345
// Continue the loop to retry
347346
}
348347
}
@@ -353,15 +352,15 @@ private async UniTask LoadHotCode(Assembly hotUpdateAss)
353352
Type type = hotUpdateAss.GetType(hotUpdateClassName);
354353
if (type == null)
355354
{
356-
await MessageBox.Show("Error", "Code exception, please contact customer service", ok: null);
355+
await Prompt.ShowDialogAsync("Error", "Code exception, please contact customer service", null, "OK");
357356
Application.Quit();
358357
return;
359358
}
360359

361360
var method = type.GetMethod(hotUpdateMethodName, BindingFlags.Public | BindingFlags.Static);
362361
if (method == null)
363362
{
364-
await MessageBox.Show("Error", "Code exception, please contact customer service", ok: null);
363+
await Prompt.ShowDialogAsync("Error", "Code exception, please contact customer service", null, "OK");
365364
Application.Quit();
366365
return;
367366
}
@@ -393,7 +392,7 @@ private async UniTask LoadHotCode(Assembly hotUpdateAss)
393392
catch (Exception e)
394393
{
395394
Debug.LogError($"Failed to invoke hot update method {hotUpdateMethodName}: {e}");
396-
await MessageBox.Show("Error", $"Function call failed: {e.Message}", ok: "Exit", no: null);
395+
await Prompt.ShowDialogAsync("Error", $"Function call failed: {e.Message}", "Exit", null);
397396
Application.Quit();
398397
}
399398
}

UnityProject/Packages/com.jasonxudeveloper.jengine.core/Runtime/Misc.meta

Lines changed: 0 additions & 3 deletions
This file was deleted.

UnityProject/Packages/com.jasonxudeveloper.jengine.core/Runtime/Misc/MessageBox.cs.meta

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)