Skip to content

Commit ea9aad9

Browse files
Add JEngine.Util package
Introduce new utility classes package for JEngine framework. Also update READMEs to document the new built-in package. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 33f835b commit ea9aad9

31 files changed

Lines changed: 3070 additions & 0 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ JEngine is a powerful Unity framework that enables **runtime hot updates** for y
3838
| Package | Description | Type |
3939
|---------|-------------|------|
4040
| **JEngine.Core** | Hot update support with secure, high-performance features | Built-in |
41+
| **JEngine.Util** | Utility classes for JEngine framework | Built-in |
4142

4243
### Additional Packages
4344
| Package | Description | Link |

README_zh_cn.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ JEngine是针对Unity开发者设计的**开箱即用**的框架,封装了强
4242
| 包名 | 描述 | 类型 |
4343
|------|------|------|
4444
| **JEngine.Core** | 支持运行时热更,附带安全且高性能的功能 | 自带 |
45+
| **JEngine.Util** | JEngine框架的工具类库 | 自带 |
4546

4647
### 扩展包
4748
| 包名 | 描述 | 链接 |

UnityProject/Packages/com.jasonxudeveloper.jengine.util/Runtime.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.

UnityProject/Packages/com.jasonxudeveloper.jengine.util/Runtime/Internal.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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// IStateStorage.cs
2+
// JEngine.Util - Polymorphic state storage interface
3+
//
4+
// Author: JasonXuDeveloper
5+
6+
using System;
7+
using System.Runtime.CompilerServices;
8+
9+
namespace JEngine.Util.Internal
10+
{
11+
/// <summary>
12+
/// Interface for polymorphic state storage with typed invocation.
13+
/// Enables invoking delegates with correctly-typed state via interface dispatch,
14+
/// avoiding boxing allocations for value types.
15+
/// </summary>
16+
internal interface IStateStorage
17+
{
18+
/// <summary>
19+
/// Invokes an Action delegate with the stored state.
20+
/// </summary>
21+
/// <param name="action">The delegate to invoke (must be <c>Action&lt;T&gt;</c> where T matches stored type).</param>
22+
void InvokeAction(Delegate action);
23+
24+
/// <summary>
25+
/// Invokes an async Func delegate with the stored state.
26+
/// </summary>
27+
/// <param name="func">The delegate to invoke (must be <c>Func&lt;T, JActionAwaitable&gt;</c>).</param>
28+
/// <returns>The awaitable result from the function.</returns>
29+
JActionAwaitable InvokeAsyncFunc(Delegate func);
30+
31+
/// <summary>
32+
/// Invokes a condition Func delegate with the stored state.
33+
/// </summary>
34+
/// <param name="condition">The delegate to invoke (must be <c>Func&lt;T, bool&gt;</c>).</param>
35+
/// <returns>The boolean result of the condition.</returns>
36+
bool InvokeCondition(Delegate condition);
37+
38+
/// <summary>
39+
/// Returns this storage instance to its type-specific pool.
40+
/// </summary>
41+
void Return();
42+
}
43+
44+
/// <summary>
45+
/// Generic state storage with per-type object pooling via <see cref="JObjectPool{T}"/>.
46+
/// </summary>
47+
/// <typeparam name="T">The type of state to store.</typeparam>
48+
/// <remarks>
49+
/// <para>
50+
/// This class avoids boxing for value types because the CLR creates a specialized
51+
/// version of the generic class for each value type, storing T directly in the class layout.
52+
/// </para>
53+
/// <para>
54+
/// Each type parameter T gets its own dedicated object pool, ensuring efficient reuse.
55+
/// </para>
56+
/// </remarks>
57+
internal sealed class StateStorage<T> : IStateStorage
58+
{
59+
private static readonly JObjectPool<StateStorage<T>> Pool = new(
60+
maxSize: 64,
61+
onReturn: static s => s.Value = default
62+
);
63+
64+
/// <summary>
65+
/// The stored state value.
66+
/// </summary>
67+
public T Value;
68+
69+
/// <summary>
70+
/// Rents a storage instance from the pool and initializes it with the specified value.
71+
/// </summary>
72+
/// <param name="value">The state value to store.</param>
73+
/// <returns>A pooled <see cref="StateStorage{T}"/> instance containing the value.</returns>
74+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
75+
public static StateStorage<T> Rent(T value)
76+
{
77+
var storage = Pool.Rent();
78+
storage.Value = value;
79+
return storage;
80+
}
81+
82+
/// <inheritdoc/>
83+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
84+
public void Return()
85+
{
86+
Pool.Return(this);
87+
}
88+
89+
/// <inheritdoc/>
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public void InvokeAction(Delegate action)
92+
{
93+
((Action<T>)action)(Value);
94+
}
95+
96+
/// <inheritdoc/>
97+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
98+
public JActionAwaitable InvokeAsyncFunc(Delegate func)
99+
{
100+
return ((Func<T, JActionAwaitable>)func)(Value);
101+
}
102+
103+
/// <inheritdoc/>
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
105+
public bool InvokeCondition(Delegate condition)
106+
{
107+
return ((Func<T, bool>)condition)(Value);
108+
}
109+
110+
/// <summary>
111+
/// Gets the approximate number of pooled instances for this type.
112+
/// </summary>
113+
public static int PoolCount => Pool.Count;
114+
115+
/// <summary>
116+
/// Clears the pool for this type.
117+
/// </summary>
118+
public static void ClearPool() => Pool.Clear();
119+
}
120+
}

UnityProject/Packages/com.jasonxudeveloper.jengine.util/Runtime/Internal/IStateStorage.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.
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// JActionRunner.cs
2+
// PlayerLoop-based driver for JAction main-thread execution (lock-free)
3+
//
4+
// Author: JasonXuDeveloper
5+
6+
using System;
7+
using System.Collections.Concurrent;
8+
using System.Collections.Generic;
9+
using System.Runtime.CompilerServices;
10+
using UnityEngine;
11+
using UnityEngine.LowLevel;
12+
#if UNITY_EDITOR
13+
using UnityEditor;
14+
#endif
15+
16+
namespace JEngine.Util.Internal
17+
{
18+
/// <summary>
19+
/// Static runner that drives <see cref="JAction"/> execution on the main thread.
20+
/// </summary>
21+
/// <remarks>
22+
/// <para>
23+
/// Uses Unity's PlayerLoopSystem API in PlayMode and EditorApplication.update in EditMode,
24+
/// avoiding any dependency on MonoBehaviour or GameObjects.
25+
/// </para>
26+
/// <para>
27+
/// Lock-free design: uses ConcurrentQueue for thread-safe registration,
28+
/// single-threaded consumption on main thread.
29+
/// </para>
30+
/// </remarks>
31+
internal static class JActionRunner
32+
{
33+
private static readonly ConcurrentQueue<JAction> PendingQueue = new();
34+
private static readonly List<JAction> ActiveActions = new(32);
35+
private static bool _runtimeInitialized;
36+
37+
#if UNITY_EDITOR
38+
private static bool _editorInitialized;
39+
#endif
40+
41+
/// <summary>
42+
/// Marker struct for identifying our update in the PlayerLoop.
43+
/// </summary>
44+
private struct JActionUpdate { }
45+
46+
#if UNITY_EDITOR
47+
[InitializeOnLoadMethod]
48+
private static void InitializeEditor()
49+
{
50+
if (_editorInitialized) return;
51+
_editorInitialized = true;
52+
53+
EditorApplication.update -= Update;
54+
EditorApplication.update += Update;
55+
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
56+
}
57+
58+
private static void OnPlayModeStateChanged(PlayModeStateChange state)
59+
{
60+
if (state == PlayModeStateChange.ExitingPlayMode)
61+
{
62+
// Drain the queue
63+
while (PendingQueue.TryDequeue(out _)) { }
64+
ActiveActions.Clear();
65+
_runtimeInitialized = false;
66+
}
67+
}
68+
#endif
69+
70+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
71+
private static void InitializeRuntime()
72+
{
73+
if (_runtimeInitialized) return;
74+
_runtimeInitialized = true;
75+
76+
var playerLoop = PlayerLoop.GetCurrentPlayerLoop();
77+
InsertUpdateSystem(ref playerLoop);
78+
PlayerLoop.SetPlayerLoop(playerLoop);
79+
}
80+
81+
private static void InsertUpdateSystem(ref PlayerLoopSystem rootLoop)
82+
{
83+
var subSystems = rootLoop.subSystemList;
84+
if (subSystems == null) return;
85+
86+
for (int i = 0; i < subSystems.Length; i++)
87+
{
88+
if (subSystems[i].type == typeof(UnityEngine.PlayerLoop.Update))
89+
{
90+
ref var updateLoop = ref subSystems[i];
91+
var updateSubSystems = updateLoop.subSystemList ?? Array.Empty<PlayerLoopSystem>();
92+
93+
var newSubSystems = new PlayerLoopSystem[updateSubSystems.Length + 1];
94+
Array.Copy(updateSubSystems, newSubSystems, updateSubSystems.Length);
95+
96+
newSubSystems[updateSubSystems.Length] = new PlayerLoopSystem
97+
{
98+
type = typeof(JActionUpdate),
99+
updateDelegate = Update
100+
};
101+
102+
updateLoop.subSystemList = newSubSystems;
103+
break;
104+
}
105+
}
106+
}
107+
108+
/// <summary>
109+
/// Registers a <see cref="JAction"/> for main-thread execution.
110+
/// </summary>
111+
/// <param name="action">The action to register. Null values are ignored.</param>
112+
/// <remarks>
113+
/// Thread-safe and lock-free via ConcurrentQueue.
114+
/// </remarks>
115+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116+
public static void Register(JAction action)
117+
{
118+
if (action == null) return;
119+
PendingQueue.Enqueue(action);
120+
}
121+
122+
private static void Update()
123+
{
124+
// Drain pending queue into active list (single-threaded, no lock needed)
125+
while (PendingQueue.TryDequeue(out var action))
126+
{
127+
ActiveActions.Add(action);
128+
}
129+
130+
if (ActiveActions.Count == 0) return;
131+
132+
// Process in reverse to allow safe removal during iteration
133+
for (int i = ActiveActions.Count - 1; i >= 0; i--)
134+
{
135+
var action = ActiveActions[i];
136+
if (action == null)
137+
{
138+
ActiveActions.RemoveAt(i);
139+
continue;
140+
}
141+
142+
try
143+
{
144+
if (action.Tick())
145+
{
146+
ActiveActions.RemoveAt(i);
147+
}
148+
}
149+
catch (Exception e)
150+
{
151+
Debug.LogException(e);
152+
ActiveActions.RemoveAt(i);
153+
}
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Gets the approximate number of currently active JActions.
159+
/// </summary>
160+
public static int ActiveCount => ActiveActions.Count + PendingQueue.Count;
161+
}
162+
}

UnityProject/Packages/com.jasonxudeveloper.jengine.util/Runtime/Internal/JActionRunner.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.

0 commit comments

Comments
 (0)