Skip to content

Commit 4c635ad

Browse files
refactor(core): extract MessageBox to separate UI package (#590)
* 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> * fix(core): address PR review feedback for Prompt abstraction - Make ShowDialogAsync thread-safe with volatile backing field and Interlocked.Exchange - Improve XML documentation for Prompt class with usage examples - Change default behavior to log error and return false (safer default) - Add namespace to PromptInitializer (JEngine) - Add TextMeshPro dependency to jengine.ui package.json - Remove JEngine.UI dependency from HotUpdate.Code (use Prompt abstraction instead) - Update EntryPoint.cs to use Prompt.ShowDialogAsync instead of MessageBox.Show Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net> * fix(core): remove redundant initialization and namespace - Use null-coalescing in getter instead of field initializer - Remove namespace from PromptInitializer (user-level code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net> * fix(core): remove JEngine.Util reference from HotUpdate.Code HotUpdate code should not directly depend on utility packages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net> --------- Signed-off-by: JasonXuDeveloper - 傑 <jason@xgamedev.net> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 17b06ca commit 4c635ad

31 files changed

+915
-227
lines changed

.claude/rules/coding-patterns.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# JEngine Coding Patterns
2+
3+
## Async Patterns
4+
5+
Use `UniTask` for async operations, not `System.Threading.Tasks.Task`:
6+
```csharp
7+
public async UniTask<bool> LoadAssetAsync() { }
8+
```
9+
10+
## Thread Safety
11+
12+
For properties accessed across callbacks:
13+
```csharp
14+
private static volatile bool _flag;
15+
public static bool Flag => _flag;
16+
```
17+
18+
## Encryption Architecture
19+
20+
JEngine supports three encryption algorithms:
21+
- **XOR** (`EncryptionOption.Xor`) - Fast, simple
22+
- **AES** (`EncryptionOption.Aes`) - Moderate security
23+
- **ChaCha20** (`EncryptionOption.ChaCha20`) - High security
24+
25+
Each has implementations for bundles and manifests in `Runtime/Encrypt/`.
26+
27+
### Adding New Encryption
28+
29+
1. Create config class in `Runtime/Encrypt/Config/`
30+
2. Implement bundle encryption in `Runtime/Encrypt/Bundle/`
31+
3. Implement manifest encryption in `Runtime/Encrypt/Manifest/`
32+
4. Add to `EncryptionOption` enum
33+
5. Update `EncryptionMapping` class
34+
35+
## ScriptableObject Configuration
36+
37+
Use `ScriptableObject` for runtime configuration:
38+
```csharp
39+
public abstract class ConfigBase<T> : ScriptableObject where T : ConfigBase<T>
40+
{
41+
public static T Instance { get; }
42+
}
43+
```
44+
45+
## Editor Scripts
46+
47+
- Use `[InitializeOnLoad]` for editor initialization
48+
- Handle Unity domain reloads properly (state resets on recompile)
49+
- Use `SessionState` or `EditorPrefs` for persistent editor state
50+
- Clean up resources in `EditorApplication.quitting`
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Commit Message Format
2+
3+
All commits should follow the Conventional Commits specification to enable automatic changelog generation.
4+
5+
## Format
6+
7+
```
8+
<type>(<scope>): <subject>
9+
10+
<body>
11+
12+
<footer>
13+
```
14+
15+
## Types
16+
17+
- `feat:` - New feature (appears in changelog as "Feature")
18+
- `fix:` - Bug fix (appears in changelog as "Fixed")
19+
- `docs:` - Documentation only changes (not in changelog)
20+
- `style:` - Code style/formatting changes (not in changelog)
21+
- `refactor:` - Code refactoring (not in changelog)
22+
- `test:` - Test changes (not in changelog)
23+
- `chore:` - Build/config changes (not in changelog)
24+
- `BREAKING CHANGE:` - Breaking changes (in footer, triggers major version bump)
25+
26+
## Scopes
27+
28+
- `core` - Changes to JEngine.Core package
29+
- `util` - Changes to JEngine.Util package
30+
- `ui` - Changes to JEngine.UI package
31+
- `ci` - Changes to CI/CD workflows
32+
- `docs` - Changes to documentation
33+
34+
## Examples
35+
36+
```bash
37+
feat(core): add ChaCha20 encryption support
38+
fix(util): resolve JAction memory leak on cancellation
39+
docs: update installation guide for Unity 2022.3
40+
refactor(core): simplify bootstrap initialization
41+
test(util): add coverage for JAction edge cases
42+
chore(ci): update GameCI Unity version to 2022.3.55f1
43+
```
44+
45+
## Breaking Changes
46+
47+
For breaking changes, include `BREAKING CHANGE:` in the footer:
48+
49+
```bash
50+
feat(core)!: redesign encryption API
51+
52+
BREAKING CHANGE: EncryptionManager.Encrypt() now requires EncryptionConfig parameter
53+
```
54+
55+
## Guidelines
56+
57+
- Keep subject line under 72 characters
58+
- Use imperative mood ("add" not "added" or "adds")
59+
- Don't capitalize first letter of subject
60+
- Don't end subject with a period
61+
- Separate subject from body with blank line
62+
- Wrap body at 72 characters
63+
- Use body to explain what and why (not how)
64+
65+
## Developer Certificate of Origin (DCO)
66+
67+
All commits must be signed off using `--signoff` (or `-s`) flag:
68+
69+
```bash
70+
git commit -s -m "feat(core): add new feature"
71+
```
72+
73+
This adds a `Signed-off-by` line certifying you have the right to submit the code under the project's license.

.claude/rules/package-creation.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Creating New JEngine Packages
2+
3+
When creating a new JEngine package:
4+
5+
1. **Version**: Start at `0.0.0` (not `1.0.0`)
6+
2. **Naming**: Use `com.jasonxudeveloper.jengine.<name>` format
7+
3. **Location**: `Packages/com.jasonxudeveloper.jengine.<name>/`
8+
9+
## Required Files
10+
11+
- `package.json` - Package manifest with version `0.0.0`
12+
- `Runtime/<Name>.asmdef` - Assembly definition
13+
- `README.md` - Package documentation (optional)
14+
15+
## Example package.json
16+
17+
```json
18+
{
19+
"name": "com.jasonxudeveloper.jengine.<name>",
20+
"version": "0.0.0",
21+
"displayName": "JEngine.<Name>",
22+
"description": "Description here.",
23+
"license": "MIT",
24+
"unity": "2022.3",
25+
"author": {
26+
"name": "Jason Xu",
27+
"email": "jason@xgamedev.com",
28+
"url": "https://github.com/JasonXuDeveloper"
29+
},
30+
"dependencies": {}
31+
}
32+
```
33+
34+
## CI Updates Required
35+
36+
1. Add package path to `.github/workflows/pr-tests.yml`
37+
2. Add release support to `.github/workflows/release.yml`
38+
3. Add new scope to commit message conventions

.github/instructions/code-review.instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ For state accessed across Unity callbacks:
3232
### 5. Namespace Compliance
3333
- Runtime: `JEngine.Core.*`
3434
- Editor: `JEngine.Core.Editor.*`
35-
- No namespace = flag for review
35+
- UI package: `JEngine.UI`
36+
- Hot update: `HotUpdate.Code`
37+
- **Exception**: `Assets/Scripts/` may contain user-level code without namespace (intentional for user customization)
3638

3739
## Common Issues to Flag
3840

.github/instructions/jengine.instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ JEngine is a Unity hot update framework using HybridCLR for runtime C# execution
1313
### Namespaces
1414
- Runtime code: `JEngine.Core` or `JEngine.Core.*`
1515
- Editor code: `JEngine.Core.Editor` or `JEngine.Core.Editor.*`
16+
- UI package: `JEngine.UI`
1617
- Hot update code: `HotUpdate.Code`
18+
- **Exception**: `Assets/Scripts/` may contain user-level code without namespace (for user customization)
1719

1820
### Async/Await
1921
Always use `UniTask` instead of `Task`:

.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

0 commit comments

Comments
 (0)