Compare commits
No commits in common. "cad7722e44562f097fa342c8d8b745058997e16d" and "c6fe229b4d6c29d22abc63988ca0e6e1a90137bb" have entirely different histories.
cad7722e44
...
c6fe229b4d
@ -1,126 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
[CustomEditor(typeof(GameObjectPoolManager))]
|
|
||||||
public sealed class GameObjectPoolEditor : UnityEditor.Editor
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, bool> _foldoutState = new Dictionary<string, bool>();
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
|
||||||
serializedObject.Update();
|
|
||||||
|
|
||||||
DrawDefaultInspector();
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
var pool = (GameObjectPoolManager)target;
|
|
||||||
if (!Application.isPlaying)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("Enter Play Mode to inspect runtime pool state.", MessageType.Info);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pool.showDetailedInfo)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
DrawRuntimeState(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool RequiresConstantRepaint()
|
|
||||||
{
|
|
||||||
var pool = target as GameObjectPoolManager;
|
|
||||||
return pool != null && Application.isPlaying && pool.showDetailedInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawRuntimeState(GameObjectPoolManager poolManager)
|
|
||||||
{
|
|
||||||
if (!poolManager.IsReady)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("GameObjectPool is initializing.", MessageType.Info);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GameObjectPoolSnapshot> snapshots = poolManager.GetDebugSnapshots();
|
|
||||||
if (snapshots.Count == 0)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("No runtime pools have been created yet.", MessageType.Info);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < snapshots.Count; i++)
|
|
||||||
{
|
|
||||||
DrawSnapshot(snapshots[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSnapshot(GameObjectPoolSnapshot snapshot)
|
|
||||||
{
|
|
||||||
if (snapshot == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string key = $"{snapshot.group}|{snapshot.assetPath}";
|
|
||||||
if (!_foldoutState.ContainsKey(key))
|
|
||||||
{
|
|
||||||
_foldoutState[key] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.BeginVertical("box");
|
|
||||||
_foldoutState[key] = EditorGUILayout.Foldout(
|
|
||||||
_foldoutState[key],
|
|
||||||
$"{snapshot.group} | {snapshot.assetPath} ({snapshot.activeCount}/{snapshot.totalCount})",
|
|
||||||
true);
|
|
||||||
|
|
||||||
if (_foldoutState[key])
|
|
||||||
{
|
|
||||||
EditorGUILayout.LabelField("Match Mode", snapshot.matchMode.ToString());
|
|
||||||
EditorGUILayout.LabelField("Loader", snapshot.loaderType.ToString());
|
|
||||||
EditorGUILayout.LabelField("Capacity", snapshot.capacity.ToString());
|
|
||||||
EditorGUILayout.LabelField("Inactive", snapshot.inactiveCount.ToString());
|
|
||||||
EditorGUILayout.LabelField("Idle Timeout", $"{snapshot.instanceIdleTimeout:F1}s");
|
|
||||||
EditorGUILayout.LabelField("Prefab Unload Delay", $"{snapshot.prefabUnloadDelay:F1}s");
|
|
||||||
EditorGUILayout.LabelField("Prefab Loaded", snapshot.prefabLoaded ? "Yes" : "No");
|
|
||||||
|
|
||||||
if (snapshot.instances.Count > 0)
|
|
||||||
{
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
EditorGUILayout.LabelField("Instances", EditorStyles.boldLabel);
|
|
||||||
for (int i = 0; i < snapshot.instances.Count; i++)
|
|
||||||
{
|
|
||||||
DrawInstance(snapshot.instances[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DrawInstance(GameObjectPoolInstanceSnapshot instance)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal("box");
|
|
||||||
EditorGUILayout.BeginVertical();
|
|
||||||
EditorGUILayout.LabelField(instance.instanceName, EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.LabelField("State", instance.isActive ? "Active" : "Inactive");
|
|
||||||
if (!instance.isActive)
|
|
||||||
{
|
|
||||||
EditorGUILayout.LabelField("Idle", $"{instance.idleDuration:F1}s");
|
|
||||||
}
|
|
||||||
EditorGUILayout.EndVertical();
|
|
||||||
EditorGUILayout.ObjectField(instance.gameObject, typeof(GameObject), true, GUILayout.Width(120));
|
|
||||||
EditorGUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 43ecbf81dd8d96d418b2d61e28e453cd
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,180 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditorInternal;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
[CustomEditor(typeof(PoolConfigScriptableObject))]
|
|
||||||
public sealed class PoolConfigScriptableObjectEditor : UnityEditor.Editor
|
|
||||||
{
|
|
||||||
private const float VerticalSpacing = 4f;
|
|
||||||
private ReorderableList _configList;
|
|
||||||
private SerializedProperty _configsProperty;
|
|
||||||
|
|
||||||
private void OnEnable()
|
|
||||||
{
|
|
||||||
_configsProperty = serializedObject.FindProperty("configs");
|
|
||||||
_configList = new ReorderableList(serializedObject, _configsProperty, true, true, true, true)
|
|
||||||
{
|
|
||||||
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Pool Configs"),
|
|
||||||
drawElementCallback = DrawElement,
|
|
||||||
elementHeightCallback = GetElementHeight
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
|
||||||
serializedObject.Update();
|
|
||||||
|
|
||||||
EditorGUILayout.HelpBox(
|
|
||||||
"每条配置定义一条匹配规则;真正的池按具体 assetPath 实例化,不再共享一个目录级总容量。",
|
|
||||||
MessageType.Info);
|
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
|
||||||
if (GUILayout.Button("Normalize"))
|
|
||||||
{
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
NormalizeAndSort(shouldSort: false);
|
|
||||||
serializedObject.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button("Normalize And Sort"))
|
|
||||||
{
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
NormalizeAndSort(shouldSort: true);
|
|
||||||
serializedObject.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
|
|
||||||
_configList.DoLayoutList();
|
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
DrawValidation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
|
|
||||||
{
|
|
||||||
SerializedProperty element = _configsProperty.GetArrayElementAtIndex(index);
|
|
||||||
rect.y += 2f;
|
|
||||||
|
|
||||||
float lineHeight = EditorGUIUtility.singleLineHeight;
|
|
||||||
float wideFieldWidth = rect.width * 0.6f;
|
|
||||||
float narrowFieldWidth = rect.width - wideFieldWidth - 6f;
|
|
||||||
|
|
||||||
SerializedProperty group = element.FindPropertyRelative("group");
|
|
||||||
SerializedProperty assetPath = element.FindPropertyRelative("assetPath");
|
|
||||||
SerializedProperty matchMode = element.FindPropertyRelative("matchMode");
|
|
||||||
SerializedProperty loaderType = element.FindPropertyRelative("resourceLoaderType");
|
|
||||||
SerializedProperty capacity = element.FindPropertyRelative("capacity");
|
|
||||||
SerializedProperty prewarmCount = element.FindPropertyRelative("prewarmCount");
|
|
||||||
SerializedProperty idleTimeout = element.FindPropertyRelative("instanceIdleTimeout");
|
|
||||||
SerializedProperty unloadDelay = element.FindPropertyRelative("prefabUnloadDelay");
|
|
||||||
SerializedProperty preloadOnInitialize = element.FindPropertyRelative("preloadOnInitialize");
|
|
||||||
|
|
||||||
Rect line1 = new Rect(rect.x, rect.y, rect.width, lineHeight);
|
|
||||||
Rect line2 = new Rect(rect.x, line1.yMax + VerticalSpacing, rect.width, lineHeight);
|
|
||||||
Rect line3Left = new Rect(rect.x, line2.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
|
|
||||||
Rect line3Right = new Rect(line3Left.xMax + 6f, line3Left.y, narrowFieldWidth, lineHeight);
|
|
||||||
Rect line4Left = new Rect(rect.x, line3Left.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
|
|
||||||
Rect line4Right = new Rect(line4Left.xMax + 6f, line4Left.y, narrowFieldWidth, lineHeight);
|
|
||||||
Rect line5Left = new Rect(rect.x, line4Left.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
|
|
||||||
Rect line5Right = new Rect(line5Left.xMax + 6f, line5Left.y, narrowFieldWidth, lineHeight);
|
|
||||||
|
|
||||||
EditorGUI.PropertyField(line1, assetPath);
|
|
||||||
EditorGUI.PropertyField(line2, group);
|
|
||||||
EditorGUI.PropertyField(line3Left, matchMode);
|
|
||||||
EditorGUI.PropertyField(line3Right, loaderType);
|
|
||||||
EditorGUI.PropertyField(line4Left, capacity);
|
|
||||||
EditorGUI.PropertyField(line4Right, prewarmCount);
|
|
||||||
EditorGUI.PropertyField(line5Left, idleTimeout);
|
|
||||||
EditorGUI.PropertyField(line5Right, unloadDelay);
|
|
||||||
|
|
||||||
Rect line6 = new Rect(rect.x, line5Left.yMax + VerticalSpacing, rect.width, lineHeight);
|
|
||||||
EditorGUI.PropertyField(line6, preloadOnInitialize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float GetElementHeight(int index)
|
|
||||||
{
|
|
||||||
float lineHeight = EditorGUIUtility.singleLineHeight;
|
|
||||||
return lineHeight * 6f + VerticalSpacing * 7f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NormalizeAndSort(bool shouldSort)
|
|
||||||
{
|
|
||||||
var asset = (PoolConfigScriptableObject)target;
|
|
||||||
asset.Normalize();
|
|
||||||
|
|
||||||
if (shouldSort)
|
|
||||||
{
|
|
||||||
asset.configs.Sort(PoolConfig.CompareByPriority);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorUtility.SetDirty(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawValidation()
|
|
||||||
{
|
|
||||||
var asset = (PoolConfigScriptableObject)target;
|
|
||||||
if (asset.configs == null || asset.configs.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> warnings = BuildWarnings(asset.configs);
|
|
||||||
for (int i = 0; i < warnings.Count; i++)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox(warnings[i], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> BuildWarnings(List<PoolConfig> configs)
|
|
||||||
{
|
|
||||||
var warnings = new List<string>();
|
|
||||||
|
|
||||||
for (int i = 0; i < configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig config = configs[i];
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
warnings.Add($"Element {i} is null.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(config.assetPath))
|
|
||||||
{
|
|
||||||
warnings.Add($"Element {i} has an empty asset path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.matchMode == PoolMatchMode.Prefix && config.preloadOnInitialize)
|
|
||||||
{
|
|
||||||
warnings.Add($"Element {i} uses Prefix matching and preloadOnInitialize. Prefix rules cannot infer a concrete asset to prewarm.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = i + 1; j < configs.Count; j++)
|
|
||||||
{
|
|
||||||
PoolConfig other = configs[j];
|
|
||||||
if (other == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool duplicate =
|
|
||||||
string.Equals(config.group, other.group, System.StringComparison.Ordinal) &&
|
|
||||||
config.matchMode == other.matchMode &&
|
|
||||||
string.Equals(config.assetPath, other.assetPath, System.StringComparison.Ordinal);
|
|
||||||
|
|
||||||
if (duplicate)
|
|
||||||
{
|
|
||||||
warnings.Add($"Duplicate rule detected between elements {i} and {j}: {config.group}:{config.assetPath}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return warnings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
Entry.prefab
41
Entry.prefab
@ -156,6 +156,37 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 1e28a727443c86c40aeb42ff20e0a343, type: 3}
|
m_Script: {fileID: 11500000, guid: 1e28a727443c86c40aeb42ff20e0a343, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!1 &2946186047994278043
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 8452422965548084857}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Fsm
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &8452422965548084857
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2946186047994278043}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 425597497363353001}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &3646865557585103128
|
--- !u!1 &3646865557585103128
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@ -237,6 +268,7 @@ Transform:
|
|||||||
- {fileID: 424925309774805088}
|
- {fileID: 424925309774805088}
|
||||||
- {fileID: 7231588671532407876}
|
- {fileID: 7231588671532407876}
|
||||||
- {fileID: 1640076400431107710}
|
- {fileID: 1640076400431107710}
|
||||||
|
- {fileID: 8452422965548084857}
|
||||||
- {fileID: 9160912643551877041}
|
- {fileID: 9160912643551877041}
|
||||||
- {fileID: 9144434048949093429}
|
- {fileID: 9144434048949093429}
|
||||||
- {fileID: 5595937395452803435}
|
- {fileID: 5595937395452803435}
|
||||||
@ -256,8 +288,6 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: 252fa1bb9e36411fb4582d0656b987bf, type: 3}
|
m_Script: {fileID: 11500000, guid: 252fa1bb9e36411fb4582d0656b987bf, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
_dontDestroyOnLoad: 1
|
|
||||||
_appScopeOrder: -10000
|
|
||||||
frameRate: 120
|
frameRate: 120
|
||||||
gameSpeed: 1
|
gameSpeed: 1
|
||||||
runInBackground: 1
|
runInBackground: 1
|
||||||
@ -274,9 +304,9 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: d544088dbfb44263bddb36004521fee5, type: 3}
|
m_Script: {fileID: 11500000, guid: d544088dbfb44263bddb36004521fee5, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
resourceComponent: {fileID: 7354290124713579766}
|
_resourceComponent: {fileID: 7354290124713579766}
|
||||||
debuggerComponent: {fileID: 8897508624174186059}
|
_debuggerComponent: {fileID: 8897508624174186059}
|
||||||
localizationComponent: {fileID: 1599215827984154130}
|
_localizationComponent: {fileID: 1599215827984154130}
|
||||||
--- !u!1 &6519989611955579811
|
--- !u!1 &6519989611955579811
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@ -325,7 +355,6 @@ MonoBehaviour:
|
|||||||
minUnloadUnusedAssetsInterval: 60
|
minUnloadUnusedAssetsInterval: 60
|
||||||
maxUnloadUnusedAssetsInterval: 300
|
maxUnloadUnusedAssetsInterval: 300
|
||||||
useSystemUnloadUnusedAssets: 1
|
useSystemUnloadUnusedAssets: 1
|
||||||
minGCCollectInterval: 30
|
|
||||||
decryptionServices:
|
decryptionServices:
|
||||||
autoUnloadBundleWhenUnused: 0
|
autoUnloadBundleWhenUnused: 0
|
||||||
_playMode: 0
|
_playMode: 0
|
||||||
|
|||||||
3
Runtime/ABase/Base/Module.meta
Normal file
3
Runtime/ABase/Base/Module.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aeb86cc258fa4378ba3b259a5d38e4df
|
||||||
|
timeCreated: 1736424998
|
||||||
42
Runtime/ABase/Base/Module/IModule.cs
Normal file
42
Runtime/ABase/Base/Module/IModule.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
namespace AlicizaX
|
||||||
|
{
|
||||||
|
public interface IModule
|
||||||
|
{
|
||||||
|
protected internal void Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleAwake
|
||||||
|
{
|
||||||
|
protected internal void Awake();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IExecuteSystem
|
||||||
|
{
|
||||||
|
abstract int Priority { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleUpdate : IExecuteSystem
|
||||||
|
{
|
||||||
|
protected internal void Update(float elapseSeconds, float realElapseSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleLateUpdate : IExecuteSystem
|
||||||
|
{
|
||||||
|
protected internal void LateUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleFixedUpdate : IExecuteSystem
|
||||||
|
{
|
||||||
|
protected internal void FixedUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleDrawGizmos : IExecuteSystem
|
||||||
|
{
|
||||||
|
protected internal void DrawGizmos();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IModuleGUI : IExecuteSystem
|
||||||
|
{
|
||||||
|
protected internal void OnGUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/ABase/Base/Module/IModule.cs.meta
Normal file
3
Runtime/ABase/Base/Module/IModule.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0ba192d5e9084d67b16b53e843e103b3
|
||||||
|
timeCreated: 1736424999
|
||||||
303
Runtime/ABase/Base/Module/ModuleSystem.cs
Normal file
303
Runtime/ABase/Base/Module/ModuleSystem.cs
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AlicizaX
|
||||||
|
{
|
||||||
|
public static class ModuleSystem
|
||||||
|
{
|
||||||
|
internal const int DesignModuleCount = 32;
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, IModule> _moduleMaps = new Dictionary<Type, IModule>(DesignModuleCount);
|
||||||
|
private static readonly GameFrameworkLinkedList<IModule> _modules = new GameFrameworkLinkedList<IModule>();
|
||||||
|
|
||||||
|
// Update systems
|
||||||
|
private static readonly GameFrameworkLinkedList<IModuleUpdate> _updateModules = new GameFrameworkLinkedList<IModuleUpdate>();
|
||||||
|
private static readonly IModuleUpdate[] _updateExecuteArray = new IModuleUpdate[DesignModuleCount];
|
||||||
|
private static int _updateExecuteCount;
|
||||||
|
|
||||||
|
// LateUpdate systems
|
||||||
|
private static readonly GameFrameworkLinkedList<IModuleLateUpdate> _lateUpdateModules = new GameFrameworkLinkedList<IModuleLateUpdate>();
|
||||||
|
private static readonly IModuleLateUpdate[] _lateUpdateExecuteArray = new IModuleLateUpdate[DesignModuleCount];
|
||||||
|
private static int _lateUpdateExecuteCount;
|
||||||
|
|
||||||
|
// FixedUpdate systems
|
||||||
|
private static readonly GameFrameworkLinkedList<IModuleFixedUpdate> _fixedUpdateModules = new GameFrameworkLinkedList<IModuleFixedUpdate>();
|
||||||
|
private static readonly IModuleFixedUpdate[] _fixedUpdateExecuteArray = new IModuleFixedUpdate[DesignModuleCount];
|
||||||
|
private static int _fixedUpdateExecuteCount;
|
||||||
|
|
||||||
|
// Gizmos systems
|
||||||
|
private static readonly GameFrameworkLinkedList<IModuleDrawGizmos> _gizmosUpdateModules = new GameFrameworkLinkedList<IModuleDrawGizmos>();
|
||||||
|
private static readonly IModuleDrawGizmos[] _gizmosUpdateExecuteArray = new IModuleDrawGizmos[DesignModuleCount];
|
||||||
|
private static int _gizmosExecuteCount;
|
||||||
|
|
||||||
|
// GUI systems
|
||||||
|
private static readonly GameFrameworkLinkedList<IModuleGUI> _guiUpdateModules = new GameFrameworkLinkedList<IModuleGUI>();
|
||||||
|
private static readonly IModuleGUI[] _guiUpdateExecuteArray = new IModuleGUI[DesignModuleCount];
|
||||||
|
private static int _guiExecuteCount;
|
||||||
|
|
||||||
|
// Dirty flags
|
||||||
|
private static bool _isExecuteListDirty;
|
||||||
|
private static bool _isLateExecuteListDirty;
|
||||||
|
private static bool _isFixedExecuteListDirty;
|
||||||
|
private static bool _isGizmosExecuteListDirty;
|
||||||
|
private static bool _isGUIExecuteListDirty;
|
||||||
|
|
||||||
|
internal static void Dispose()
|
||||||
|
{
|
||||||
|
// Clear all modules and execute arrays
|
||||||
|
_updateModules.Clear();
|
||||||
|
Array.Clear(_updateExecuteArray, 0, _updateExecuteArray.Length);
|
||||||
|
_updateExecuteCount = 0;
|
||||||
|
|
||||||
|
_lateUpdateModules.Clear();
|
||||||
|
Array.Clear(_lateUpdateExecuteArray, 0, _lateUpdateExecuteArray.Length);
|
||||||
|
_lateUpdateExecuteCount = 0;
|
||||||
|
|
||||||
|
_fixedUpdateModules.Clear();
|
||||||
|
Array.Clear(_fixedUpdateExecuteArray, 0, _fixedUpdateExecuteArray.Length);
|
||||||
|
_fixedUpdateExecuteCount = 0;
|
||||||
|
|
||||||
|
_gizmosUpdateModules.Clear();
|
||||||
|
Array.Clear(_gizmosUpdateExecuteArray, 0, _gizmosUpdateExecuteArray.Length);
|
||||||
|
_gizmosExecuteCount = 0;
|
||||||
|
|
||||||
|
_guiUpdateModules.Clear();
|
||||||
|
Array.Clear(_guiUpdateExecuteArray, 0, _guiUpdateExecuteArray.Length);
|
||||||
|
_guiExecuteCount = 0;
|
||||||
|
|
||||||
|
// Dispose all modules
|
||||||
|
for (var current = _modules.Last; current != null; current = current.Previous)
|
||||||
|
{
|
||||||
|
current.Value.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_modules.Clear();
|
||||||
|
_moduleMaps.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public System
|
||||||
|
|
||||||
|
public static T RegisterModule<T, TImple>() where T : IModule where TImple : class, T, new()
|
||||||
|
{
|
||||||
|
Type interfaceType = typeof(T);
|
||||||
|
Type implType = typeof(TImple);
|
||||||
|
if (!interfaceType.IsInterface)
|
||||||
|
{
|
||||||
|
throw new GameFrameworkException(Utility.Text.Format("You must register module by interface, but '{0}' is not.", interfaceType.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!implType.IsClass || implType.IsInterface || implType.IsAbstract)
|
||||||
|
{
|
||||||
|
throw new GameFrameworkException(Utility.Text.Format("You must register module by Class and not Interface and Abstract, but '{0}' is not.", implType.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeof(IModule).IsAssignableFrom(interfaceType))
|
||||||
|
{
|
||||||
|
throw new GameFrameworkException(Utility.Text.Format("Module must implement IModule.", interfaceType.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetModule(interfaceType) != null)
|
||||||
|
{
|
||||||
|
Log.Error("Already Register {0}", interfaceType.FullName);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
TImple impl = new TImple();
|
||||||
|
return (T)SetModuleInstance(interfaceType, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T GetModule<T>() where T : class
|
||||||
|
{
|
||||||
|
Type interfaceType = typeof(T);
|
||||||
|
if (!interfaceType.IsInterface)
|
||||||
|
{
|
||||||
|
throw new GameFrameworkException(Utility.Text.Format("You must get module by interface, but '{0}' is not.", interfaceType.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetModule(interfaceType) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IModule GetModule(Type moduleType)
|
||||||
|
{
|
||||||
|
return _moduleMaps.TryGetValue(moduleType, out var ret) ? ret : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IModule SetModuleInstance<TImpl>(Type interfaceType, TImpl impl) where TImpl : class, IModule
|
||||||
|
{
|
||||||
|
_moduleMaps[interfaceType] = impl;
|
||||||
|
_modules.AddLast(impl);
|
||||||
|
|
||||||
|
if (impl is IModuleAwake awakeSystem)
|
||||||
|
{
|
||||||
|
awakeSystem.Awake();
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckSystemLife(impl);
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static void CheckSystemLife(IModule module)
|
||||||
|
{
|
||||||
|
if (module is IModuleUpdate updateSystem)
|
||||||
|
{
|
||||||
|
BindSystemLife(updateSystem, _updateModules, ref _isExecuteListDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module is IModuleLateUpdate lateUpdate)
|
||||||
|
{
|
||||||
|
BindSystemLife(lateUpdate, _lateUpdateModules, ref _isLateExecuteListDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module is IModuleFixedUpdate fixedUpdate)
|
||||||
|
{
|
||||||
|
BindSystemLife(fixedUpdate, _fixedUpdateModules, ref _isFixedExecuteListDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module is IModuleDrawGizmos drawGizmosUpdate)
|
||||||
|
{
|
||||||
|
BindSystemLife(drawGizmosUpdate, _gizmosUpdateModules, ref _isGizmosExecuteListDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module is IModuleGUI guiUpdate)
|
||||||
|
{
|
||||||
|
BindSystemLife(guiUpdate, _guiUpdateModules, ref _isGUIExecuteListDirty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BindSystemLife<T>(T system, GameFrameworkLinkedList<T> updateModule, ref bool executeDirty) where T : IExecuteSystem
|
||||||
|
{
|
||||||
|
var current = updateModule.First;
|
||||||
|
while (current != null && system.Priority <= current.Value.Priority)
|
||||||
|
{
|
||||||
|
current = current.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
updateModule.AddBefore(current, system);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateModule.AddLast(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
executeDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region BuildExecuteList
|
||||||
|
|
||||||
|
private static void BuildExecuteList()
|
||||||
|
{
|
||||||
|
if (!_isExecuteListDirty) return;
|
||||||
|
_isExecuteListDirty = false;
|
||||||
|
_updateExecuteCount = 0;
|
||||||
|
foreach (var module in _updateModules)
|
||||||
|
{
|
||||||
|
if (_updateExecuteCount >= _updateExecuteArray.Length) break;
|
||||||
|
_updateExecuteArray[_updateExecuteCount++] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildLateExecuteList()
|
||||||
|
{
|
||||||
|
if (!_isLateExecuteListDirty) return;
|
||||||
|
_isLateExecuteListDirty = false;
|
||||||
|
_lateUpdateExecuteCount = 0;
|
||||||
|
foreach (var module in _lateUpdateModules)
|
||||||
|
{
|
||||||
|
if (_lateUpdateExecuteCount >= _lateUpdateExecuteArray.Length) break;
|
||||||
|
_lateUpdateExecuteArray[_lateUpdateExecuteCount++] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildFixedExecuteList()
|
||||||
|
{
|
||||||
|
if (!_isFixedExecuteListDirty) return;
|
||||||
|
_isFixedExecuteListDirty = false;
|
||||||
|
_fixedUpdateExecuteCount = 0;
|
||||||
|
foreach (var module in _fixedUpdateModules)
|
||||||
|
{
|
||||||
|
if (_fixedUpdateExecuteCount >= _fixedUpdateExecuteArray.Length) break;
|
||||||
|
_fixedUpdateExecuteArray[_fixedUpdateExecuteCount++] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildGizmosExecuteList()
|
||||||
|
{
|
||||||
|
if (!_isGizmosExecuteListDirty) return;
|
||||||
|
_isGizmosExecuteListDirty = false;
|
||||||
|
_gizmosExecuteCount = 0;
|
||||||
|
foreach (var module in _gizmosUpdateModules)
|
||||||
|
{
|
||||||
|
if (_gizmosExecuteCount >= _gizmosUpdateExecuteArray.Length) break;
|
||||||
|
_gizmosUpdateExecuteArray[_gizmosExecuteCount++] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildGUIExecuteList()
|
||||||
|
{
|
||||||
|
if (!_isGUIExecuteListDirty) return;
|
||||||
|
_isGUIExecuteListDirty = false;
|
||||||
|
_guiExecuteCount = 0;
|
||||||
|
foreach (var module in _guiUpdateModules)
|
||||||
|
{
|
||||||
|
if (_guiExecuteCount >= _guiUpdateExecuteArray.Length) break;
|
||||||
|
_guiUpdateExecuteArray[_guiExecuteCount++] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region RunExecuteList
|
||||||
|
|
||||||
|
internal static void UpdateExecuteList(float elapseSeconds, float realElapseSeconds)
|
||||||
|
{
|
||||||
|
BuildExecuteList();
|
||||||
|
for (int i = 0; i < _updateExecuteCount; i++)
|
||||||
|
{
|
||||||
|
_updateExecuteArray[i].Update(elapseSeconds, realElapseSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateLateExecuteList()
|
||||||
|
{
|
||||||
|
BuildLateExecuteList();
|
||||||
|
for (int i = 0; i < _lateUpdateExecuteCount; i++)
|
||||||
|
{
|
||||||
|
_lateUpdateExecuteArray[i].LateUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateFixedExecuteList()
|
||||||
|
{
|
||||||
|
BuildFixedExecuteList();
|
||||||
|
for (int i = 0; i < _fixedUpdateExecuteCount; i++)
|
||||||
|
{
|
||||||
|
_fixedUpdateExecuteArray[i].FixedUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateGizmosExecuteList()
|
||||||
|
{
|
||||||
|
BuildGizmosExecuteList();
|
||||||
|
for (int i = 0; i < _gizmosExecuteCount; i++)
|
||||||
|
{
|
||||||
|
_gizmosUpdateExecuteArray[i].DrawGizmos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void UpdateGUIExecuteList()
|
||||||
|
{
|
||||||
|
BuildGUIExecuteList();
|
||||||
|
for (int i = 0; i < _guiExecuteCount; i++)
|
||||||
|
{
|
||||||
|
_guiUpdateExecuteArray[i].OnGUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/ABase/Base/Module/ModuleSystem.cs.meta
Normal file
3
Runtime/ABase/Base/Module/ModuleSystem.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b9fa1d3abb954b57989b94b4c77e45ce
|
||||||
|
timeCreated: 1736928479
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using AlicizaX.ObjectPool;
|
using AlicizaX.ObjectPool;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -10,7 +10,7 @@ namespace AlicizaX
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DisallowMultipleComponent]
|
[DisallowMultipleComponent]
|
||||||
[UnityEngine.Scripting.Preserve]
|
[UnityEngine.Scripting.Preserve]
|
||||||
public sealed class RootModule : AppServiceRoot
|
public sealed class RootModule : MonoBehaviour
|
||||||
{
|
{
|
||||||
private static RootModule _instance = null;
|
private static RootModule _instance = null;
|
||||||
|
|
||||||
@ -134,9 +134,8 @@ namespace AlicizaX
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 游戏框架组件初始化。
|
/// 游戏框架组件初始化。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
base.Awake();
|
|
||||||
_instance = this;
|
_instance = this;
|
||||||
DontDestroyOnLoad(this);
|
DontDestroyOnLoad(this);
|
||||||
Utility.Unity.MakeEntity(transform);
|
Utility.Unity.MakeEntity(transform);
|
||||||
@ -167,7 +166,7 @@ namespace AlicizaX
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void Shutdown()
|
internal void Shutdown()
|
||||||
{
|
{
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
Utility.Unity.Shutdown();
|
Utility.Unity.Shutdown();
|
||||||
@ -175,11 +174,43 @@ namespace AlicizaX
|
|||||||
Utility.Marshal.FreeCachedHGlobal();
|
Utility.Marshal.FreeCachedHGlobal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
ModuleSystem.UpdateExecuteList(Time.deltaTime, Time.unscaledDeltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
ModuleSystem.UpdateLateExecuteList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
ModuleSystem.UpdateFixedExecuteList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDrawGizmos()
|
||||||
|
{
|
||||||
|
ModuleSystem.UpdateGizmosExecuteList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
ModuleSystem.UpdateGUIExecuteList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnDestroy()
|
||||||
|
{
|
||||||
|
await UniTask.Yield();
|
||||||
|
ModuleSystem.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void OnLowMemory()
|
private void OnLowMemory()
|
||||||
{
|
{
|
||||||
Log.Warning("Low memory reported...");
|
Log.Warning("Low memory reported...");
|
||||||
|
|
||||||
IObjectPoolService objectPoolModule = AppServices.Require<IObjectPoolService>();
|
IObjectPoolModule objectPoolModule = ModuleSystem.GetModule<IObjectPoolModule>();
|
||||||
if (objectPoolModule != null)
|
if (objectPoolModule != null)
|
||||||
{
|
{
|
||||||
objectPoolModule.ReleaseAllUnused();
|
objectPoolModule.ReleaseAllUnused();
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 420d45a81b5c11e478b3a5ebe958914f
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 48e89f2eceb320547b4986db840128da
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 框架内置的 App Scope 标记类,生命周期与 ServiceWorld 相同。
|
|
||||||
/// </summary>
|
|
||||||
public sealed class AppScope { }
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public static class AppServices
|
|
||||||
{
|
|
||||||
private static ServiceWorld _world;
|
|
||||||
|
|
||||||
public static bool HasWorld => _world != null;
|
|
||||||
|
|
||||||
public static ServiceWorld World
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_world == null)
|
|
||||||
throw new InvalidOperationException("ServiceWorld has not been created yet.");
|
|
||||||
return _world;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ServiceScope App => World.AppScope;
|
|
||||||
|
|
||||||
public static ServiceWorld EnsureWorld(int appScopeOrder = -10000)
|
|
||||||
{
|
|
||||||
if (_world == null)
|
|
||||||
_world = new ServiceWorld(appScopeOrder);
|
|
||||||
return _world;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Scope 管理 ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public static ServiceScope CreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
=> World.CreateScope<TScope>(order);
|
|
||||||
|
|
||||||
public static ServiceScope GetOrCreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
=> World.GetOrCreateScope<TScope>(order);
|
|
||||||
|
|
||||||
public static bool TryGetScope<TScope>(out ServiceScope scope) where TScope : class
|
|
||||||
{
|
|
||||||
if (_world == null) { scope = null; return false; }
|
|
||||||
return _world.TryGetScope<TScope>(out scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool DestroyScope<TScope>() where TScope : class
|
|
||||||
{
|
|
||||||
if (_world == null) return false;
|
|
||||||
return _world.DestroyScope<TScope>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Service 查找 ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public static bool TryGet<T>(out T service) where T : class, IService
|
|
||||||
{
|
|
||||||
if (_world == null) { service = null; return false; }
|
|
||||||
return _world.TryGet(out service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Require<T>() where T : class, IService
|
|
||||||
=> World.Require<T>();
|
|
||||||
|
|
||||||
// ── 生命周期 ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public static void Shutdown()
|
|
||||||
{
|
|
||||||
if (_world == null) return;
|
|
||||||
_world.Dispose();
|
|
||||||
_world = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 7186431b5e61b3c4f9865bd9901ce831
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 标记一个 IService 实现来自 MonoBehaviour。
|
|
||||||
/// ServiceScope 通过此接口识别 Mono 服务,避免 Core 层依赖 UnityEngine。
|
|
||||||
/// </summary>
|
|
||||||
public interface IMonoService : IService { }
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 64c7431bc2d38d24594f92006ac398d9
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public interface IService
|
|
||||||
{
|
|
||||||
void Initialize(ServiceContext context);
|
|
||||||
void Destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1cab09453acbb93498bd781202713656
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public interface IServiceOrder
|
|
||||||
{
|
|
||||||
int Order { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f66ea6b1b8a144b4184b2b078305f8df
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public interface IServiceTickable
|
|
||||||
{
|
|
||||||
void Tick(float deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IServiceLateTickable
|
|
||||||
{
|
|
||||||
void LateTick(float deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IServiceFixedTickable
|
|
||||||
{
|
|
||||||
void FixedTick(float fixedDeltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IServiceGizmoDrawable
|
|
||||||
{
|
|
||||||
void DrawGizmos();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a7ff388c5465f4c439bf85bb76c2d786
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
internal sealed class ReferenceComparer<T> : IEqualityComparer<T>
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
public static readonly ReferenceComparer<T> Instance = new ReferenceComparer<T>();
|
|
||||||
|
|
||||||
private ReferenceComparer() { }
|
|
||||||
|
|
||||||
public bool Equals(T x, T y) => ReferenceEquals(x, y);
|
|
||||||
|
|
||||||
public int GetHashCode(T obj)
|
|
||||||
=> System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f3afc1438ca0eba4892ac93b1165d60d
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public abstract class ServiceBase : IService
|
|
||||||
{
|
|
||||||
public ServiceContext Context { get; private set; }
|
|
||||||
|
|
||||||
public bool IsInitialized { get; private set; }
|
|
||||||
|
|
||||||
protected ServiceWorld World => Context.World;
|
|
||||||
|
|
||||||
protected ServiceScope Scope => Context.Scope;
|
|
||||||
|
|
||||||
void IService.Initialize(ServiceContext context)
|
|
||||||
{
|
|
||||||
if (IsInitialized)
|
|
||||||
throw new System.InvalidOperationException($"{GetType().FullName} is already initialized.");
|
|
||||||
|
|
||||||
Context = context;
|
|
||||||
IsInitialized = true;
|
|
||||||
OnInitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IService.Destroy()
|
|
||||||
{
|
|
||||||
if (!IsInitialized) return;
|
|
||||||
|
|
||||||
OnDestroyService();
|
|
||||||
IsInitialized = false;
|
|
||||||
Context = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void OnInitialize();
|
|
||||||
|
|
||||||
protected abstract void OnDestroyService();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: af65a59b2e4b11b419fc09505ecd1c88
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public readonly struct ServiceContext
|
|
||||||
{
|
|
||||||
public ServiceContext(ServiceWorld world, ServiceScope scope)
|
|
||||||
{
|
|
||||||
World = world;
|
|
||||||
Scope = scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceWorld World { get; }
|
|
||||||
|
|
||||||
public ServiceScope Scope { get; }
|
|
||||||
|
|
||||||
public ServiceScope AppScope => World.AppScope;
|
|
||||||
|
|
||||||
public T Require<T>() where T : class, IService
|
|
||||||
=> World.Require<T>(Scope);
|
|
||||||
|
|
||||||
public bool TryGet<T>(out T service) where T : class, IService
|
|
||||||
=> World.TryGet(Scope, out service);
|
|
||||||
|
|
||||||
public ServiceScope CreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
=> World.CreateScope<TScope>(order);
|
|
||||||
|
|
||||||
public ServiceScope GetOrCreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
=> World.GetOrCreateScope<TScope>(order);
|
|
||||||
|
|
||||||
public bool TryGetScope<TScope>(out ServiceScope scope) where TScope : class
|
|
||||||
=> World.TryGetScope<TScope>(out scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fd30fae6aee72a04db7507d194215b40
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
internal static class ServiceContractUtility
|
|
||||||
{
|
|
||||||
private static readonly HashSet<Type> ExcludedContracts = new HashSet<Type>
|
|
||||||
{
|
|
||||||
typeof(IService),
|
|
||||||
typeof(IServiceTickable),
|
|
||||||
typeof(IServiceLateTickable),
|
|
||||||
typeof(IServiceFixedTickable),
|
|
||||||
typeof(IServiceGizmoDrawable),
|
|
||||||
typeof(IServiceOrder),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache for the common no-extraContracts path — contracts per concrete type never change.
|
|
||||||
private static readonly Dictionary<Type, List<Type>> _contractCache = new Dictionary<Type, List<Type>>();
|
|
||||||
|
|
||||||
public static List<Type> Collect(Type serviceType, IReadOnlyList<Type> extraContracts)
|
|
||||||
{
|
|
||||||
if (extraContracts == null || extraContracts.Count == 0)
|
|
||||||
{
|
|
||||||
if (_contractCache.TryGetValue(serviceType, out var cached))
|
|
||||||
return cached;
|
|
||||||
var result = BuildContracts(serviceType);
|
|
||||||
_contractCache[serviceType] = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra contracts path: build fresh, validate, append extras.
|
|
||||||
var contracts = BuildContracts(serviceType);
|
|
||||||
contracts = new List<Type>(contracts); // don't mutate the cached list
|
|
||||||
var unique = new HashSet<Type>(contracts);
|
|
||||||
for (var i = 0; i < extraContracts.Count; i++)
|
|
||||||
{
|
|
||||||
var extraContract = extraContracts[i];
|
|
||||||
ValidateExtraContract(serviceType, extraContract);
|
|
||||||
if (unique.Add(extraContract)) contracts.Add(extraContract);
|
|
||||||
}
|
|
||||||
return contracts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Type> BuildContracts(Type serviceType)
|
|
||||||
{
|
|
||||||
var contracts = new List<Type> { serviceType };
|
|
||||||
var unique = new HashSet<Type> { serviceType };
|
|
||||||
|
|
||||||
var interfaces = serviceType.GetInterfaces();
|
|
||||||
for (var i = 0; i < interfaces.Length; i++)
|
|
||||||
{
|
|
||||||
var contract = interfaces[i];
|
|
||||||
if (!typeof(IService).IsAssignableFrom(contract)) continue;
|
|
||||||
if (ExcludedContracts.Contains(contract)) continue;
|
|
||||||
if (unique.Add(contract)) contracts.Add(contract);
|
|
||||||
}
|
|
||||||
return contracts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateExtraContract(Type serviceType, Type contract)
|
|
||||||
{
|
|
||||||
if (contract == null)
|
|
||||||
throw new ArgumentNullException(nameof(contract));
|
|
||||||
|
|
||||||
if (!typeof(IService).IsAssignableFrom(contract))
|
|
||||||
throw new InvalidOperationException($"{contract.FullName} must inherit {nameof(IService)}.");
|
|
||||||
|
|
||||||
if (!contract.IsAssignableFrom(serviceType))
|
|
||||||
throw new InvalidOperationException($"{serviceType.FullName} does not implement {contract.FullName}.");
|
|
||||||
|
|
||||||
if (ExcludedContracts.Contains(contract))
|
|
||||||
throw new InvalidOperationException($"{contract.FullName} cannot be used as a service contract.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 95c468fca1e70ab4b931d26d98af3fd9
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,272 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public sealed class ServiceScope : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Dictionary<Type, IService> _servicesByContract = new Dictionary<Type, IService>();
|
|
||||||
private readonly Dictionary<IService, List<Type>> _contractsByService = new Dictionary<IService, List<Type>>(ReferenceComparer<IService>.Instance);
|
|
||||||
private readonly List<IService> _registrationOrder = new List<IService>();
|
|
||||||
|
|
||||||
private readonly List<IServiceTickable> _tickables = new List<IServiceTickable>();
|
|
||||||
private readonly List<IServiceLateTickable> _lateTickables = new List<IServiceLateTickable>();
|
|
||||||
private readonly List<IServiceFixedTickable> _fixedTickables = new List<IServiceFixedTickable>();
|
|
||||||
private readonly List<IServiceGizmoDrawable> _gizmoDrawables = new List<IServiceGizmoDrawable>();
|
|
||||||
|
|
||||||
private IServiceTickable[] _tickableSnapshot = Array.Empty<IServiceTickable>();
|
|
||||||
private IServiceLateTickable[] _lateTickableSnapshot = Array.Empty<IServiceLateTickable>();
|
|
||||||
private IServiceFixedTickable[] _fixedTickableSnapshot = Array.Empty<IServiceFixedTickable>();
|
|
||||||
private IServiceGizmoDrawable[] _gizmoSnapshot = Array.Empty<IServiceGizmoDrawable>();
|
|
||||||
|
|
||||||
private bool _tickablesDirty;
|
|
||||||
private bool _lateTickablesDirty;
|
|
||||||
private bool _fixedTickablesDirty;
|
|
||||||
private bool _gizmoDrawablesDirty;
|
|
||||||
|
|
||||||
internal ServiceScope(ServiceWorld world, string name, int order)
|
|
||||||
{
|
|
||||||
World = world ?? throw new ArgumentNullException(nameof(world));
|
|
||||||
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name;
|
|
||||||
Order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceWorld World { get; }
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public int Order { get; }
|
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
|
||||||
|
|
||||||
public T Register<T>(T service, params Type[] extraContracts)
|
|
||||||
where T : class, IService
|
|
||||||
{
|
|
||||||
EnsureNotDisposed();
|
|
||||||
|
|
||||||
if (service == null)
|
|
||||||
throw new ArgumentNullException(nameof(service));
|
|
||||||
|
|
||||||
ValidateService(service);
|
|
||||||
|
|
||||||
if (_contractsByService.ContainsKey(service))
|
|
||||||
throw new InvalidOperationException($"Service {service.GetType().FullName} is already registered in scope {Name}.");
|
|
||||||
|
|
||||||
var contracts = ServiceContractUtility.Collect(service.GetType(), extraContracts);
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
{
|
|
||||||
var contract = contracts[i];
|
|
||||||
if (_servicesByContract.TryGetValue(contract, out var existing))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Scope {Name} already contains contract {contract.FullName} bound to {existing.GetType().FullName}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_contractsByService.Add(service, contracts);
|
|
||||||
_registrationOrder.Add(service);
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
_servicesByContract.Add(contracts[i], service);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
service.Initialize(new ServiceContext(World, this));
|
|
||||||
AddToLifecycleLists(service);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
RemoveBindings(service);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unregister<T>() where T : class, IService
|
|
||||||
{
|
|
||||||
if (!_servicesByContract.TryGetValue(typeof(T), out var service))
|
|
||||||
return false;
|
|
||||||
return Unregister(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unregister(IService service)
|
|
||||||
{
|
|
||||||
if (service == null || !_contractsByService.ContainsKey(service))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
RemoveFromLifecycleLists(service);
|
|
||||||
RemoveBindings(service);
|
|
||||||
service.Destroy();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGet<T>(out T service) where T : class, IService
|
|
||||||
{
|
|
||||||
if (_servicesByContract.TryGetValue(typeof(T), out var raw))
|
|
||||||
{
|
|
||||||
service = raw as T;
|
|
||||||
return service != null;
|
|
||||||
}
|
|
||||||
service = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Require<T>() where T : class, IService
|
|
||||||
{
|
|
||||||
if (TryGet(out T service)) return service;
|
|
||||||
throw new InvalidOperationException($"Scope {Name} does not contain service {typeof(T).FullName}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasContract(Type contractType)
|
|
||||||
=> _servicesByContract.ContainsKey(contractType);
|
|
||||||
|
|
||||||
internal void Tick(float deltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetTickSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void LateTick(float deltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetLateTickSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void FixedTick(float fixedDeltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetFixedTickSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void DrawGizmos()
|
|
||||||
{
|
|
||||||
var snapshot = GetGizmoSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (IsDisposed) return;
|
|
||||||
|
|
||||||
var snapshot = _registrationOrder.ToArray();
|
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var service = snapshot[i];
|
|
||||||
if (!_contractsByService.ContainsKey(service)) continue;
|
|
||||||
RemoveFromLifecycleLists(service);
|
|
||||||
RemoveContractBindings(service); // skip _registrationOrder.Remove — we clear below
|
|
||||||
service.Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
_registrationOrder.Clear();
|
|
||||||
IsDisposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureNotDisposed()
|
|
||||||
{
|
|
||||||
if (IsDisposed) throw new ObjectDisposedException(Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateService(IService service)
|
|
||||||
{
|
|
||||||
if (service is IMonoService &&
|
|
||||||
(service is IServiceTickable ||
|
|
||||||
service is IServiceLateTickable ||
|
|
||||||
service is IServiceFixedTickable ||
|
|
||||||
service is IServiceGizmoDrawable))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Mono service {service.GetType().FullName} cannot implement tick lifecycle interfaces.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddToLifecycleLists(IService service)
|
|
||||||
{
|
|
||||||
if (service is IServiceTickable tickable) { _tickables.Add(tickable); _tickablesDirty = true; }
|
|
||||||
if (service is IServiceLateTickable late) { _lateTickables.Add(late); _lateTickablesDirty = true; }
|
|
||||||
if (service is IServiceFixedTickable fixed_) { _fixedTickables.Add(fixed_); _fixedTickablesDirty = true; }
|
|
||||||
if (service is IServiceGizmoDrawable gizmo) { _gizmoDrawables.Add(gizmo); _gizmoDrawablesDirty = true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveFromLifecycleLists(IService service)
|
|
||||||
{
|
|
||||||
if (service is IServiceTickable tickable && _tickables.Remove(tickable)) _tickablesDirty = true;
|
|
||||||
if (service is IServiceLateTickable late && _lateTickables.Remove(late)) _lateTickablesDirty = true;
|
|
||||||
if (service is IServiceFixedTickable fixed_ && _fixedTickables.Remove(fixed_)) _fixedTickablesDirty = true;
|
|
||||||
if (service is IServiceGizmoDrawable gizmo && _gizmoDrawables.Remove(gizmo)) _gizmoDrawablesDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveBindings(IService service)
|
|
||||||
{
|
|
||||||
if (_contractsByService.TryGetValue(service, out var contracts))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
_servicesByContract.Remove(contracts[i]);
|
|
||||||
}
|
|
||||||
_contractsByService.Remove(service);
|
|
||||||
_registrationOrder.Remove(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used during full Dispose — skips the O(n) _registrationOrder.Remove since we clear the list afterwards.
|
|
||||||
private void RemoveContractBindings(IService service)
|
|
||||||
{
|
|
||||||
if (_contractsByService.TryGetValue(service, out var contracts))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
_servicesByContract.Remove(contracts[i]);
|
|
||||||
}
|
|
||||||
_contractsByService.Remove(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IServiceTickable[] GetTickSnapshot()
|
|
||||||
{
|
|
||||||
if (_tickablesDirty)
|
|
||||||
{
|
|
||||||
_tickables.Sort(CompareByOrder);
|
|
||||||
_tickableSnapshot = _tickables.Count > 0 ? _tickables.ToArray() : Array.Empty<IServiceTickable>();
|
|
||||||
_tickablesDirty = false;
|
|
||||||
}
|
|
||||||
return _tickableSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IServiceLateTickable[] GetLateTickSnapshot()
|
|
||||||
{
|
|
||||||
if (_lateTickablesDirty)
|
|
||||||
{
|
|
||||||
_lateTickables.Sort(CompareByOrder);
|
|
||||||
_lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty<IServiceLateTickable>();
|
|
||||||
_lateTickablesDirty = false;
|
|
||||||
}
|
|
||||||
return _lateTickableSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IServiceFixedTickable[] GetFixedTickSnapshot()
|
|
||||||
{
|
|
||||||
if (_fixedTickablesDirty)
|
|
||||||
{
|
|
||||||
_fixedTickables.Sort(CompareByOrder);
|
|
||||||
_fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty<IServiceFixedTickable>();
|
|
||||||
_fixedTickablesDirty = false;
|
|
||||||
}
|
|
||||||
return _fixedTickableSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IServiceGizmoDrawable[] GetGizmoSnapshot()
|
|
||||||
{
|
|
||||||
if (_gizmoDrawablesDirty)
|
|
||||||
{
|
|
||||||
_gizmoDrawables.Sort(CompareByOrder);
|
|
||||||
_gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty<IServiceGizmoDrawable>();
|
|
||||||
_gizmoDrawablesDirty = false;
|
|
||||||
}
|
|
||||||
return _gizmoSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CompareByOrder<T>(T a, T b)
|
|
||||||
{
|
|
||||||
var left = a is IServiceOrder oa ? oa.Order : 0;
|
|
||||||
var right = b is IServiceOrder ob ? ob.Order : 0;
|
|
||||||
return left.CompareTo(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 90a7d784a3aff2f4f832fa67901e6931
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public sealed class ServiceWorld : IDisposable
|
|
||||||
{
|
|
||||||
private readonly List<ServiceScope> _scopes = new List<ServiceScope>();
|
|
||||||
private readonly Dictionary<Type, ServiceScope> _scopesByType = new Dictionary<Type, ServiceScope>();
|
|
||||||
|
|
||||||
private ServiceScope[] _scopeSnapshot = Array.Empty<ServiceScope>();
|
|
||||||
private bool _scopesDirty;
|
|
||||||
|
|
||||||
public ServiceWorld(int appScopeOrder = -10000)
|
|
||||||
{
|
|
||||||
AppScope = CreateScopeInternal(typeof(AppScope), appScopeOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceScope AppScope { get; }
|
|
||||||
|
|
||||||
// ── Scope 管理(Type-based) ────────────────────────────────────────────
|
|
||||||
|
|
||||||
public ServiceScope CreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
{
|
|
||||||
var type = typeof(TScope);
|
|
||||||
if (_scopesByType.ContainsKey(type))
|
|
||||||
throw new InvalidOperationException($"Scope {type.Name} already exists.");
|
|
||||||
return CreateScopeInternal(type, order);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceScope GetOrCreateScope<TScope>(int order = 0) where TScope : class
|
|
||||||
{
|
|
||||||
var type = typeof(TScope);
|
|
||||||
if (_scopesByType.TryGetValue(type, out var existing))
|
|
||||||
return existing;
|
|
||||||
return CreateScopeInternal(type, order);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetScope<TScope>(out ServiceScope scope) where TScope : class
|
|
||||||
=> _scopesByType.TryGetValue(typeof(TScope), out scope);
|
|
||||||
|
|
||||||
public ServiceScope GetScope<TScope>() where TScope : class
|
|
||||||
{
|
|
||||||
if (TryGetScope<TScope>(out var scope)) return scope;
|
|
||||||
throw new InvalidOperationException($"Scope {typeof(TScope).Name} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DestroyScope<TScope>() where TScope : class
|
|
||||||
{
|
|
||||||
if (typeof(TScope) == typeof(AppScope))
|
|
||||||
throw new InvalidOperationException("AppScope can only be destroyed when the world is disposed.");
|
|
||||||
|
|
||||||
var type = typeof(TScope);
|
|
||||||
if (!_scopesByType.TryGetValue(type, out var scope))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_scopesByType.Remove(type);
|
|
||||||
_scopes.Remove(scope);
|
|
||||||
_scopesDirty = true;
|
|
||||||
scope.Dispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Service 查找 ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public bool TryGet<T>(out T service) where T : class, IService
|
|
||||||
=> TryGet(null, out service);
|
|
||||||
|
|
||||||
public bool TryGet<T>(ServiceScope preferredScope, out T service) where T : class, IService
|
|
||||||
{
|
|
||||||
if (preferredScope != null && !preferredScope.IsDisposed && preferredScope.TryGet(out service))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var snapshot = GetScopeSnapshot();
|
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var scope = snapshot[i];
|
|
||||||
if (ReferenceEquals(scope, preferredScope)) continue;
|
|
||||||
if (scope.TryGet(out service)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
service = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Require<T>() where T : class, IService => Require<T>(null);
|
|
||||||
|
|
||||||
public T Require<T>(ServiceScope preferredScope) where T : class, IService
|
|
||||||
{
|
|
||||||
if (TryGet(preferredScope, out T service)) return service;
|
|
||||||
throw new InvalidOperationException($"Service {typeof(T).FullName} was not found in any active scope.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Tick ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public void Tick(float deltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetScopeSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LateTick(float deltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetScopeSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FixedTick(float fixedDeltaTime)
|
|
||||||
{
|
|
||||||
var snapshot = GetScopeSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawGizmos()
|
|
||||||
{
|
|
||||||
var snapshot = GetScopeSnapshot();
|
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Dispose ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
var snapshot = _scopes.ToArray();
|
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
|
||||||
snapshot[i].Dispose();
|
|
||||||
_scopes.Clear();
|
|
||||||
_scopesByType.Clear();
|
|
||||||
_scopeSnapshot = Array.Empty<ServiceScope>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 内部 ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private ServiceScope CreateScopeInternal(Type scopeType, int order)
|
|
||||||
{
|
|
||||||
var scope = new ServiceScope(this, scopeType.Name, order);
|
|
||||||
_scopes.Add(scope);
|
|
||||||
_scopesByType.Add(scopeType, scope);
|
|
||||||
_scopes.Sort(CompareScopeOrder);
|
|
||||||
_scopesDirty = true;
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ServiceScope[] GetScopeSnapshot()
|
|
||||||
{
|
|
||||||
if (_scopesDirty)
|
|
||||||
{
|
|
||||||
_scopeSnapshot = _scopes.Count > 0 ? _scopes.ToArray() : Array.Empty<ServiceScope>();
|
|
||||||
_scopesDirty = false;
|
|
||||||
}
|
|
||||||
return _scopeSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CompareScopeOrder(ServiceScope left, ServiceScope right)
|
|
||||||
=> left.Order.CompareTo(right.Order);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: d48a8032734a55647a565ee72471c5c2
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: c69106e6f9633554ea75fbc122821e2a
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
using Cysharp.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
[DefaultExecutionOrder(-32000)]
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
public class AppServiceRoot : MonoBehaviour
|
|
||||||
{
|
|
||||||
private static AppServiceRoot s_activeRoot;
|
|
||||||
|
|
||||||
[SerializeField] private bool _dontDestroyOnLoad = true;
|
|
||||||
[SerializeField] private int _appScopeOrder = -10000;
|
|
||||||
|
|
||||||
private bool _ownsWorld;
|
|
||||||
|
|
||||||
protected virtual void Awake()
|
|
||||||
{
|
|
||||||
if (s_activeRoot != null && s_activeRoot != this)
|
|
||||||
{
|
|
||||||
enabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_activeRoot = this;
|
|
||||||
|
|
||||||
if (_dontDestroyOnLoad)
|
|
||||||
DontDestroyOnLoad(gameObject);
|
|
||||||
|
|
||||||
var createdWorld = !AppServices.HasWorld;
|
|
||||||
var world = AppServices.EnsureWorld(_appScopeOrder);
|
|
||||||
_ownsWorld = createdWorld;
|
|
||||||
|
|
||||||
if (createdWorld)
|
|
||||||
RegisterAppServices(world.AppScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Update()
|
|
||||||
{
|
|
||||||
if (AppServices.HasWorld) AppServices.World.Tick(Time.deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void LateUpdate()
|
|
||||||
{
|
|
||||||
if (AppServices.HasWorld) AppServices.World.LateTick(Time.deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void FixedUpdate()
|
|
||||||
{
|
|
||||||
if (AppServices.HasWorld) AppServices.World.FixedTick(Time.fixedDeltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnDrawGizmos()
|
|
||||||
{
|
|
||||||
if (AppServices.HasWorld) AppServices.World.DrawGizmos();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual async void OnDestroy()
|
|
||||||
{
|
|
||||||
if (s_activeRoot == this)
|
|
||||||
s_activeRoot = null;
|
|
||||||
|
|
||||||
if (_ownsWorld && AppServices.HasWorld)
|
|
||||||
{
|
|
||||||
await UniTask.Yield();
|
|
||||||
AppServices.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void RegisterAppServices(ServiceScope appScope) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f1e04080395e4e649a43e98b673c5911
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Mono 服务基类(不自动注册,适合需要手动控制注册时机的场景)。
|
|
||||||
/// </summary>
|
|
||||||
public abstract class MonoServiceBehaviour : MonoBehaviour, IMonoService
|
|
||||||
{
|
|
||||||
public ServiceContext Context { get; private set; }
|
|
||||||
|
|
||||||
public bool IsInitialized { get; private set; }
|
|
||||||
|
|
||||||
protected ServiceWorld World => Context.World;
|
|
||||||
|
|
||||||
protected ServiceScope Scope => Context.Scope;
|
|
||||||
|
|
||||||
void IService.Initialize(ServiceContext context)
|
|
||||||
{
|
|
||||||
if (IsInitialized)
|
|
||||||
throw new System.InvalidOperationException($"{GetType().FullName} is already initialized.");
|
|
||||||
|
|
||||||
Context = context;
|
|
||||||
IsInitialized = true;
|
|
||||||
OnServiceInitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IService.Destroy()
|
|
||||||
{
|
|
||||||
if (!IsInitialized) return;
|
|
||||||
|
|
||||||
OnServiceDestroy();
|
|
||||||
IsInitialized = false;
|
|
||||||
Context = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnServiceInitialize() { }
|
|
||||||
|
|
||||||
protected virtual void OnServiceDestroy() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mono 服务基类(自动注册到 <typeparamref name="TScope"/>)。
|
|
||||||
/// <para>
|
|
||||||
/// 场景服务:<c>_dontDestroyOnLoad = false</c>(默认),销毁时自动注销。<br/>
|
|
||||||
/// 跨场景服务:<c>_dontDestroyOnLoad = true</c>,首个实例持久化并注册;
|
|
||||||
/// 后续场景中出现的重复实例自动销毁自身。
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// 子类通过 <see cref="OnAwake"/> 执行额外的 Awake 逻辑,
|
|
||||||
/// 通过 <see cref="OnServiceInitialize"/> 执行注册后的初始化,
|
|
||||||
/// 通过 <see cref="OnServiceDestroy"/> 执行注销前的清理。
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public abstract class MonoServiceBehaviour<TScope> : MonoServiceBehaviour
|
|
||||||
where TScope : class
|
|
||||||
{
|
|
||||||
[SerializeField] private bool _dontDestroyOnLoad = false;
|
|
||||||
|
|
||||||
// 注意:使用 Start 而非 Awake 注册,确保 GameServiceRoot.Awake(创建 World)必然先于此执行。
|
|
||||||
// DefaultExecutionOrder 会影响所有生命周期(含 Awake),用 Start 可彻底规避执行顺序陷阱。
|
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
OnAwake();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
var scope = AppServices.GetOrCreateScope<TScope>();
|
|
||||||
|
|
||||||
// 跨场景重复实例检测:契约已被占用则销毁自身
|
|
||||||
if (scope.HasContract(GetType()))
|
|
||||||
{
|
|
||||||
Destroy(gameObject);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer DontDestroyOnLoad until after the duplicate check so rejected
|
|
||||||
// duplicates are never moved to the DontDestroyOnLoad scene.
|
|
||||||
if (_dontDestroyOnLoad)
|
|
||||||
DontDestroyOnLoad(gameObject);
|
|
||||||
|
|
||||||
scope.Register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
if (!IsInitialized) return;
|
|
||||||
if (!AppServices.HasWorld) return;
|
|
||||||
if (!AppServices.TryGetScope<TScope>(out var scope)) return;
|
|
||||||
|
|
||||||
scope.Unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在 Awake 阶段执行(早于 Start 中的自动注册)。
|
|
||||||
/// 适合缓存组件引用等不依赖服务系统的初始化。
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnAwake() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: af63e3465b03eaf4ca3a82aebcab6e26
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fe4fa3a609da4cd188473e4d4914897c
|
|
||||||
timeCreated: 1774439835
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.Serialization;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public enum PoolResourceLoaderType
|
|
||||||
{
|
|
||||||
AssetBundle = 0,
|
|
||||||
Resources = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PoolMatchMode
|
|
||||||
{
|
|
||||||
Exact = 0,
|
|
||||||
Prefix = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 对象池配置项。
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
public sealed class PoolConfig
|
|
||||||
{
|
|
||||||
public const string DefaultGroup = "Default";
|
|
||||||
public const int DefaultCapacity = 8;
|
|
||||||
public const float DefaultInstanceIdleTimeout = 30f;
|
|
||||||
public const float DefaultPrefabUnloadDelay = 60f;
|
|
||||||
|
|
||||||
public string group = DefaultGroup;
|
|
||||||
|
|
||||||
[FormerlySerializedAs("asset")]
|
|
||||||
public string assetPath;
|
|
||||||
|
|
||||||
public PoolMatchMode matchMode = PoolMatchMode.Exact;
|
|
||||||
public PoolResourceLoaderType resourceLoaderType = PoolResourceLoaderType.AssetBundle;
|
|
||||||
|
|
||||||
[FormerlySerializedAs("time")]
|
|
||||||
[Min(0f)]
|
|
||||||
public float instanceIdleTimeout = DefaultInstanceIdleTimeout;
|
|
||||||
|
|
||||||
[Min(0f)]
|
|
||||||
public float prefabUnloadDelay = DefaultPrefabUnloadDelay;
|
|
||||||
|
|
||||||
[FormerlySerializedAs("poolCount")]
|
|
||||||
[Min(1)]
|
|
||||||
public int capacity = DefaultCapacity;
|
|
||||||
|
|
||||||
[Min(0)]
|
|
||||||
public int prewarmCount;
|
|
||||||
|
|
||||||
public bool preloadOnInitialize;
|
|
||||||
|
|
||||||
public void Normalize()
|
|
||||||
{
|
|
||||||
group = string.IsNullOrWhiteSpace(group) ? DefaultGroup : group.Trim();
|
|
||||||
assetPath = NormalizeAssetPath(assetPath);
|
|
||||||
capacity = Mathf.Max(1, capacity);
|
|
||||||
prewarmCount = Mathf.Clamp(prewarmCount, 0, capacity);
|
|
||||||
instanceIdleTimeout = Mathf.Max(0f, instanceIdleTimeout);
|
|
||||||
prefabUnloadDelay = Mathf.Max(0f, prefabUnloadDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Matches(string requestedAssetPath, string requestedGroup = null)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(assetPath) || string.IsNullOrWhiteSpace(requestedAssetPath))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(requestedGroup) &&
|
|
||||||
!string.Equals(group, requestedGroup, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchMode switch
|
|
||||||
{
|
|
||||||
PoolMatchMode.Exact => string.Equals(requestedAssetPath, assetPath, StringComparison.Ordinal),
|
|
||||||
PoolMatchMode.Prefix => requestedAssetPath.StartsWith(assetPath, StringComparison.Ordinal),
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildResolvedPoolKey(string resolvedAssetPath)
|
|
||||||
{
|
|
||||||
return $"{group}|{(int)matchMode}|{assetPath}|{(int)resourceLoaderType}|{resolvedAssetPath}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int CompareByPriority(PoolConfig left, PoolConfig right)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(left, right))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left == null)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (right == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int modeCompare = left.matchMode.CompareTo(right.matchMode);
|
|
||||||
if (modeCompare != 0)
|
|
||||||
{
|
|
||||||
return modeCompare;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pathLengthCompare = right.assetPath.Length.CompareTo(left.assetPath.Length);
|
|
||||||
if (pathLengthCompare != 0)
|
|
||||||
{
|
|
||||||
return pathLengthCompare;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Compare(left.group, right.group, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string NormalizeAssetPath(string value)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.Trim().Replace('\\', '/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 7de4b0d73b4145b6bd836ec9d2c832d9
|
|
||||||
timeCreated: 1774439841
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
[CreateAssetMenu(fileName = "PoolConfig", menuName = "GameplaySystem/PoolConfig", order = 10)]
|
|
||||||
public class PoolConfigScriptableObject : ScriptableObject
|
|
||||||
{
|
|
||||||
public List<PoolConfig> configs = new List<PoolConfig>();
|
|
||||||
|
|
||||||
public void Normalize()
|
|
||||||
{
|
|
||||||
if (configs == null)
|
|
||||||
{
|
|
||||||
configs = new List<PoolConfig>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < configs.Count; i++)
|
|
||||||
{
|
|
||||||
configs[i]?.Normalize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
private void OnValidate()
|
|
||||||
{
|
|
||||||
Normalize();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
1369
Runtime/ABase/GameObjectPool/GameObjectPool.cs
Normal file
1369
Runtime/ABase/GameObjectPool/GameObjectPool.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: d205821b5dae0264fb6c5d1ea78f9b43
|
guid: ce0e8ead006ba324eaf2410a3dd556a5
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
322
Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs
Normal file
322
Runtime/ABase/GameObjectPool/GameObjectPoolEditor.cs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
namespace AlicizaX
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(GameObjectPool))]
|
||||||
|
public class GameObjectPoolEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private bool[] _poolFoldouts;
|
||||||
|
private bool[] _prefabFoldouts;
|
||||||
|
private float _lastRefreshTime;
|
||||||
|
private const float AUTO_REFRESH_INTERVAL = 0.1f;
|
||||||
|
|
||||||
|
// 缓存序列化属性,避免重复查找
|
||||||
|
private SerializedProperty _poolInfosProperty;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_poolInfosProperty = serializedObject.FindProperty("poolInfos");
|
||||||
|
_lastRefreshTime = Time.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
var pool = (GameObjectPool)target;
|
||||||
|
|
||||||
|
// 更新序列化对象
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
// 绘制默认Inspector
|
||||||
|
DrawDefaultInspector();
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 手动刷新按钮
|
||||||
|
if (GUILayout.Button("刷新池状态信息"))
|
||||||
|
{
|
||||||
|
RefreshPoolInfo(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要自动刷新
|
||||||
|
bool shouldAutoRefresh = EditorApplication.isPlaying && pool.showDetailedInfo &&
|
||||||
|
Selection.activeGameObject == pool.gameObject &&
|
||||||
|
Time.time - _lastRefreshTime > AUTO_REFRESH_INTERVAL;
|
||||||
|
|
||||||
|
if (shouldAutoRefresh)
|
||||||
|
{
|
||||||
|
RefreshPoolInfo(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pool.showDetailedInfo)
|
||||||
|
{
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField("对象池详细信息", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
// 重新获取属性以确保数据是最新的
|
||||||
|
_poolInfosProperty = serializedObject.FindProperty("poolInfos");
|
||||||
|
|
||||||
|
if (_poolInfosProperty != null && _poolInfosProperty.arraySize > 0)
|
||||||
|
{
|
||||||
|
DrawPoolInfos();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("暂无池信息,请等待系统初始化或点击刷新按钮", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示自动刷新状态
|
||||||
|
if (Selection.activeGameObject == pool.gameObject)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Inspector正在自动刷新 (仅在选中时)", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用修改的属性
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshPoolInfo(GameObjectPool pool)
|
||||||
|
{
|
||||||
|
pool.RefreshInspectorInfo();
|
||||||
|
_lastRefreshTime = Time.time;
|
||||||
|
serializedObject.Update(); // 立即更新序列化对象
|
||||||
|
|
||||||
|
// 标记需要重绘
|
||||||
|
if (Selection.activeGameObject == pool.gameObject)
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(pool);
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPoolInfos()
|
||||||
|
{
|
||||||
|
int poolCount = _poolInfosProperty.arraySize;
|
||||||
|
|
||||||
|
// 确保折叠状态数组大小正确
|
||||||
|
if (_poolFoldouts == null || _poolFoldouts.Length != poolCount)
|
||||||
|
{
|
||||||
|
bool[] oldPoolFoldouts = _poolFoldouts;
|
||||||
|
bool[] oldPrefabFoldouts = _prefabFoldouts;
|
||||||
|
|
||||||
|
_poolFoldouts = new bool[poolCount];
|
||||||
|
_prefabFoldouts = new bool[poolCount];
|
||||||
|
|
||||||
|
// 保持之前的折叠状态
|
||||||
|
if (oldPoolFoldouts != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Mathf.Min(oldPoolFoldouts.Length, poolCount); i++)
|
||||||
|
{
|
||||||
|
_poolFoldouts[i] = oldPoolFoldouts[i];
|
||||||
|
if (oldPrefabFoldouts != null && i < oldPrefabFoldouts.Length)
|
||||||
|
{
|
||||||
|
_prefabFoldouts[i] = oldPrefabFoldouts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < poolCount; i++)
|
||||||
|
{
|
||||||
|
DrawPoolInfo(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPoolInfo(int poolIndex)
|
||||||
|
{
|
||||||
|
var poolInfo = _poolInfosProperty.GetArrayElementAtIndex(poolIndex);
|
||||||
|
if (poolInfo == null) return;
|
||||||
|
|
||||||
|
var configAssetProp = poolInfo.FindPropertyRelative("configAsset");
|
||||||
|
var totalObjectsProp = poolInfo.FindPropertyRelative("totalObjects");
|
||||||
|
var maxCountProp = poolInfo.FindPropertyRelative("maxCount");
|
||||||
|
var activeObjectsProp = poolInfo.FindPropertyRelative("activeObjects");
|
||||||
|
|
||||||
|
if (configAssetProp == null || totalObjectsProp == null || maxCountProp == null || activeObjectsProp == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string configAsset = configAssetProp.stringValue;
|
||||||
|
int totalObjects = totalObjectsProp.intValue;
|
||||||
|
int maxCount = maxCountProp.intValue;
|
||||||
|
int activeObjects = activeObjectsProp.intValue;
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical("box");
|
||||||
|
|
||||||
|
// 使用Rect布局来精确控制Foldout的大小
|
||||||
|
Rect rect = EditorGUILayout.GetControlRect();
|
||||||
|
Rect foldoutRect = new Rect(rect.x, rect.y, 15, rect.height);
|
||||||
|
Rect progressRect = new Rect(rect.x + 20, rect.y, rect.width - 120, rect.height);
|
||||||
|
Rect labelRect = new Rect(rect.x + rect.width - 95, rect.y, 95, rect.height);
|
||||||
|
|
||||||
|
// 绘制折叠按钮
|
||||||
|
_poolFoldouts[poolIndex] = EditorGUI.Foldout(foldoutRect, _poolFoldouts[poolIndex], GUIContent.none);
|
||||||
|
|
||||||
|
// 使用率进度条
|
||||||
|
float usage = maxCount > 0 ? (float)totalObjects / maxCount : 0f;
|
||||||
|
EditorGUI.ProgressBar(progressRect, usage, $"{configAsset} ({totalObjects}/{maxCount})");
|
||||||
|
|
||||||
|
// 活跃对象数
|
||||||
|
EditorGUI.LabelField(labelRect, $"活跃:{activeObjects}", EditorStyles.miniLabel);
|
||||||
|
|
||||||
|
if (_poolFoldouts[poolIndex])
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
DrawPoolDetails(poolInfo, poolIndex);
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPoolDetails(SerializedProperty poolInfo, int poolIndex)
|
||||||
|
{
|
||||||
|
var configAssetProp = poolInfo.FindPropertyRelative("configAsset");
|
||||||
|
var maxCountProp = poolInfo.FindPropertyRelative("maxCount");
|
||||||
|
var expireTimeProp = poolInfo.FindPropertyRelative("expireTime");
|
||||||
|
var loadedPrefabsProp = poolInfo.FindPropertyRelative("loadedPrefabs");
|
||||||
|
|
||||||
|
if (configAssetProp != null)
|
||||||
|
EditorGUILayout.LabelField($"配置路径: {configAssetProp.stringValue}");
|
||||||
|
if (maxCountProp != null)
|
||||||
|
EditorGUILayout.LabelField($"最大数量: {maxCountProp.intValue}");
|
||||||
|
if (expireTimeProp != null)
|
||||||
|
EditorGUILayout.LabelField($"过期时间: {expireTimeProp.floatValue}s");
|
||||||
|
if (loadedPrefabsProp != null)
|
||||||
|
EditorGUILayout.LabelField($"已加载预制体: {loadedPrefabsProp.intValue}");
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 绘制预制体引用信息
|
||||||
|
DrawPrefabRefs(poolInfo, poolIndex);
|
||||||
|
|
||||||
|
// 绘制对象详细信息
|
||||||
|
DrawObjectDetails(poolInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPrefabRefs(SerializedProperty poolInfo, int poolIndex)
|
||||||
|
{
|
||||||
|
var prefabRefsProp = poolInfo.FindPropertyRelative("prefabRefs");
|
||||||
|
if (prefabRefsProp == null || prefabRefsProp.arraySize <= 0) return;
|
||||||
|
|
||||||
|
// 使用简单的Foldout,不指定宽度
|
||||||
|
_prefabFoldouts[poolIndex] = EditorGUILayout.Foldout(_prefabFoldouts[poolIndex], "预制体引用信息:");
|
||||||
|
|
||||||
|
if (_prefabFoldouts[poolIndex])
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
for (int j = 0; j < prefabRefsProp.arraySize; j++)
|
||||||
|
{
|
||||||
|
DrawPrefabRefInfo(prefabRefsProp.GetArrayElementAtIndex(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPrefabRefInfo(SerializedProperty prefabRef)
|
||||||
|
{
|
||||||
|
if (prefabRef == null) return;
|
||||||
|
|
||||||
|
var assetPathProp = prefabRef.FindPropertyRelative("assetPath");
|
||||||
|
var refCountProp = prefabRef.FindPropertyRelative("refCount");
|
||||||
|
var lastAccessTimeProp = prefabRef.FindPropertyRelative("lastAccessTime");
|
||||||
|
var prefabObjProp = prefabRef.FindPropertyRelative("prefab");
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal("box");
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical();
|
||||||
|
if (assetPathProp != null)
|
||||||
|
EditorGUILayout.LabelField($"{System.IO.Path.GetFileName(assetPathProp.stringValue)}", EditorStyles.boldLabel);
|
||||||
|
if (refCountProp != null)
|
||||||
|
EditorGUILayout.LabelField($"引用计数: {refCountProp.intValue}", EditorStyles.miniLabel);
|
||||||
|
if (lastAccessTimeProp != null)
|
||||||
|
EditorGUILayout.LabelField($"最后访问: {(Time.time - lastAccessTimeProp.floatValue):F1}秒前", EditorStyles.miniLabel);
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
if (prefabObjProp != null)
|
||||||
|
EditorGUILayout.ObjectField(prefabObjProp.objectReferenceValue, typeof(GameObject), false, GUILayout.Width(100));
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawObjectDetails(SerializedProperty poolInfo)
|
||||||
|
{
|
||||||
|
var objectsProp = poolInfo.FindPropertyRelative("objects");
|
||||||
|
if (objectsProp == null || objectsProp.arraySize <= 0) return;
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("对象详情:", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
for (int j = 0; j < objectsProp.arraySize; j++)
|
||||||
|
{
|
||||||
|
DrawObjectInfo(objectsProp.GetArrayElementAtIndex(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawObjectInfo(SerializedProperty obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
var objNameProp = obj.FindPropertyRelative("objectName");
|
||||||
|
var objAssetPathProp = obj.FindPropertyRelative("assetPath");
|
||||||
|
var isActiveProp = obj.FindPropertyRelative("isActive");
|
||||||
|
var remainingTimeProp = obj.FindPropertyRelative("remainingTime");
|
||||||
|
var expireProgressProp = obj.FindPropertyRelative("expireProgress");
|
||||||
|
var gameObjectProp = obj.FindPropertyRelative("gameObject");
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal("box");
|
||||||
|
|
||||||
|
// 状态颜色指示器
|
||||||
|
bool isActive = isActiveProp?.boolValue ?? false;
|
||||||
|
var statusColor = isActive ? Color.green : Color.yellow;
|
||||||
|
var prevColor = GUI.color;
|
||||||
|
GUI.color = statusColor;
|
||||||
|
EditorGUILayout.LabelField("●", GUILayout.Width(15));
|
||||||
|
GUI.color = prevColor;
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical();
|
||||||
|
|
||||||
|
// 对象名称和路径
|
||||||
|
string objName = objNameProp?.stringValue ?? "Unknown";
|
||||||
|
string objAssetPath = objAssetPathProp?.stringValue ?? "";
|
||||||
|
EditorGUILayout.LabelField($"{objName} ({System.IO.Path.GetFileName(objAssetPath)})", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.LabelField($"状态: {(isActive ? "活跃" : "空闲")}", EditorStyles.miniLabel);
|
||||||
|
|
||||||
|
// 过期进度条
|
||||||
|
if (!isActive && remainingTimeProp != null && expireProgressProp != null)
|
||||||
|
{
|
||||||
|
float remainingTime = remainingTimeProp.floatValue;
|
||||||
|
float expireProgress = expireProgressProp.floatValue;
|
||||||
|
|
||||||
|
if (remainingTime >= 0)
|
||||||
|
{
|
||||||
|
Rect expireRect = GUILayoutUtility.GetRect(100, 16, GUILayout.ExpandWidth(true), GUILayout.Height(16));
|
||||||
|
EditorGUI.ProgressBar(expireRect, expireProgress, $"释放倒计时: {remainingTime:F1}s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
// GameObject引用
|
||||||
|
if (gameObjectProp != null)
|
||||||
|
EditorGUILayout.ObjectField(gameObjectProp.objectReferenceValue, typeof(GameObject), true, GUILayout.Width(100));
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool RequiresConstantRepaint()
|
||||||
|
{
|
||||||
|
// 只有在选中对象池时才需要持续重绘
|
||||||
|
var pool = target as GameObjectPool;
|
||||||
|
return pool != null && pool.showDetailedInfo && Selection.activeGameObject == pool.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: df436879f8854a95b5a92e8b77772189
|
||||||
|
timeCreated: 1773109368
|
||||||
@ -1,587 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using AlicizaX.Resource.Runtime;
|
|
||||||
using Cysharp.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public sealed class GameObjectPoolManager : MonoServiceBehaviour<GameObjectPoolManager>
|
|
||||||
{
|
|
||||||
private static readonly Comparison<GameObjectPoolSnapshot> SnapshotComparer = (left, right) =>
|
|
||||||
{
|
|
||||||
if (left == null && right == null) return 0;
|
|
||||||
if (left == null) return 1;
|
|
||||||
if (right == null) return -1;
|
|
||||||
int groupCompare = string.Compare(left.group, right.group, StringComparison.Ordinal);
|
|
||||||
return groupCompare != 0 ? groupCompare : string.Compare(left.assetPath, right.assetPath, StringComparison.Ordinal);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Header("检查间隔")] public float checkInterval = 10f;
|
|
||||||
|
|
||||||
[Header("配置路径")] public string poolConfigPath = "Assets/Bundles/Configs/PoolConfig";
|
|
||||||
|
|
||||||
[Header("Inspector显示设置")] public bool showDetailedInfo = true;
|
|
||||||
|
|
||||||
[SerializeField] internal Transform poolContainer;
|
|
||||||
|
|
||||||
private const PoolResourceLoaderType DefaultDirectLoadResourceLoaderType = PoolResourceLoaderType.AssetBundle;
|
|
||||||
|
|
||||||
private readonly Dictionary<string, RuntimePrefabPool> _poolsByKey = new Dictionary<string, RuntimePrefabPool>(StringComparer.Ordinal);
|
|
||||||
private readonly Dictionary<string, PoolConfig> _resolvedConfigCache = new Dictionary<string, PoolConfig>(StringComparer.Ordinal);
|
|
||||||
private readonly Dictionary<string, PoolResourceLoaderType> _groupLoaderCache = new Dictionary<string, PoolResourceLoaderType>(StringComparer.Ordinal);
|
|
||||||
private readonly Dictionary<GameObject, RuntimePrefabPool> _ownersByObject = new Dictionary<GameObject, RuntimePrefabPool>();
|
|
||||||
private readonly Dictionary<PoolResourceLoaderType, IResourceLoader> _resourceLoaders = new Dictionary<PoolResourceLoaderType, IResourceLoader>();
|
|
||||||
private readonly List<PoolConfig> _configs = new List<PoolConfig>();
|
|
||||||
private readonly List<GameObjectPoolSnapshot> _debugSnapshots = new List<GameObjectPoolSnapshot>();
|
|
||||||
|
|
||||||
private CancellationTokenSource _shutdownTokenSource;
|
|
||||||
private UniTask _initializeTask;
|
|
||||||
private bool _initializationCompleted;
|
|
||||||
private Exception _initializationException;
|
|
||||||
private float _lastCleanupTime;
|
|
||||||
|
|
||||||
private bool _isShuttingDown;
|
|
||||||
|
|
||||||
public bool IsReady => _initializationCompleted && _initializationException == null;
|
|
||||||
|
|
||||||
protected override void OnServiceInitialize()
|
|
||||||
{
|
|
||||||
_shutdownTokenSource = new CancellationTokenSource();
|
|
||||||
EnsureDefaultResourceLoaders();
|
|
||||||
EnsurePoolContainer();
|
|
||||||
_initializeTask = InitializeAsync(_shutdownTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnServiceDestroy()
|
|
||||||
{
|
|
||||||
_shutdownTokenSource?.Cancel();
|
|
||||||
ClearAllPools();
|
|
||||||
|
|
||||||
if (poolContainer != null)
|
|
||||||
{
|
|
||||||
Destroy(poolContainer.gameObject);
|
|
||||||
poolContainer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_shutdownTokenSource?.Dispose();
|
|
||||||
_shutdownTokenSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask InitializeAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await UniTask.WaitUntil(() => YooAsset.YooAssets.Initialized, cancellationToken: cancellationToken);
|
|
||||||
LoadConfigs();
|
|
||||||
_lastCleanupTime = Time.time;
|
|
||||||
_initializationCompleted = true;
|
|
||||||
await PrewarmConfiguredPoolsAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_initializationException = exception;
|
|
||||||
_initializationCompleted = true;
|
|
||||||
Log.Error($"GameObjectPool initialization failed: {exception}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
if (!IsReady)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Time.time - _lastCleanupTime < checkInterval)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PerformCleanup();
|
|
||||||
_lastCleanupTime = Time.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetResourceLoader(IResourceLoader resourceLoader)
|
|
||||||
{
|
|
||||||
SetResourceLoader(PoolResourceLoaderType.AssetBundle, resourceLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetResourceLoader(PoolResourceLoaderType loaderType, IResourceLoader resourceLoader)
|
|
||||||
{
|
|
||||||
if (resourceLoader == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(resourceLoader));
|
|
||||||
}
|
|
||||||
|
|
||||||
_resourceLoaders[loaderType] = resourceLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameObject GetGameObject(string assetPath, Transform parent = null)
|
|
||||||
{
|
|
||||||
EnsureReadyForSyncUse();
|
|
||||||
return GetGameObjectInternal(PoolConfig.NormalizeAssetPath(assetPath), null, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameObject GetGameObjectByGroup(string group, string assetPath, Transform parent = null)
|
|
||||||
{
|
|
||||||
EnsureReadyForSyncUse();
|
|
||||||
return GetGameObjectInternal(PoolConfig.NormalizeAssetPath(assetPath), group, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> GetGameObjectAsync(
|
|
||||||
string assetPath,
|
|
||||||
Transform parent = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await EnsureInitializedAsync(cancellationToken);
|
|
||||||
return await GetGameObjectInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), null, parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> GetGameObjectAsyncByGroup(
|
|
||||||
string group,
|
|
||||||
string assetPath,
|
|
||||||
Transform parent = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await EnsureInitializedAsync(cancellationToken);
|
|
||||||
return await GetGameObjectInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), group, parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Release(GameObject gameObject)
|
|
||||||
{
|
|
||||||
if (gameObject == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_ownersByObject.TryGetValue(gameObject, out RuntimePrefabPool pool))
|
|
||||||
{
|
|
||||||
pool.Release(gameObject);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Warning($"Trying to release untracked GameObject '{gameObject.name}'. Destroying it.");
|
|
||||||
Destroy(gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Preload(string assetPath, int count = 1)
|
|
||||||
{
|
|
||||||
EnsureReadyForSyncUse();
|
|
||||||
PreloadInternal(PoolConfig.NormalizeAssetPath(assetPath), null, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PreloadByGroup(string group, string assetPath, int count = 1)
|
|
||||||
{
|
|
||||||
EnsureReadyForSyncUse();
|
|
||||||
PreloadInternal(PoolConfig.NormalizeAssetPath(assetPath), group, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask PreloadAsync(string assetPath, int count = 1, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await EnsureInitializedAsync(cancellationToken);
|
|
||||||
await PreloadInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), null, count, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask PreloadAsyncByGroup(
|
|
||||||
string group,
|
|
||||||
string assetPath,
|
|
||||||
int count = 1,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await EnsureInitializedAsync(cancellationToken);
|
|
||||||
await PreloadInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), group, count, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForceCleanup()
|
|
||||||
{
|
|
||||||
if (!IsReady)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PerformCleanup();
|
|
||||||
_lastCleanupTime = Time.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearAllPools()
|
|
||||||
{
|
|
||||||
_isShuttingDown = true;
|
|
||||||
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
|
||||||
{
|
|
||||||
pool.Shutdown();
|
|
||||||
MemoryPool.Release(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isShuttingDown = false;
|
|
||||||
|
|
||||||
_poolsByKey.Clear();
|
|
||||||
_ownersByObject.Clear();
|
|
||||||
_resolvedConfigCache.Clear();
|
|
||||||
_groupLoaderCache.Clear();
|
|
||||||
ReleaseDebugSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GameObjectPoolSnapshot> GetDebugSnapshots()
|
|
||||||
{
|
|
||||||
ReleaseDebugSnapshots();
|
|
||||||
|
|
||||||
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
|
||||||
{
|
|
||||||
_debugSnapshots.Add(pool.CreateSnapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
_debugSnapshots.Sort(SnapshotComparer);
|
|
||||||
|
|
||||||
return _debugSnapshots;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RegisterOwnedObject(GameObject gameObject, RuntimePrefabPool pool)
|
|
||||||
{
|
|
||||||
if (gameObject == null || pool == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ownersByObject[gameObject] = pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UnregisterOwnedObject(GameObject gameObject)
|
|
||||||
{
|
|
||||||
if (gameObject == null || _isShuttingDown)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ownersByObject.Remove(gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameObject GetGameObjectInternal(string assetPath, string group, Transform parent)
|
|
||||||
{
|
|
||||||
PoolConfig config = ResolveConfig(assetPath, group);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return LoadUnpooled(assetPath, group, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
|
||||||
return pool.Acquire(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask<GameObject> GetGameObjectInternalAsync(
|
|
||||||
string assetPath,
|
|
||||||
string group,
|
|
||||||
Transform parent,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
PoolConfig config = ResolveConfig(assetPath, group);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return await LoadUnpooledAsync(assetPath, group, parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
|
||||||
return await pool.AcquireAsync(parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreloadInternal(string assetPath, string group, int count)
|
|
||||||
{
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PoolConfig config = ResolveConfig(assetPath, group);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
Log.Warning($"Asset '{assetPath}' has no matching pool config. Preload skipped.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
|
||||||
pool.Warmup(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask PreloadInternalAsync(
|
|
||||||
string assetPath,
|
|
||||||
string group,
|
|
||||||
int count,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PoolConfig config = ResolveConfig(assetPath, group);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
Log.Warning($"Asset '{assetPath}' has no matching pool config. Preload skipped.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
|
||||||
await pool.WarmupAsync(count, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RuntimePrefabPool GetOrCreatePool(PoolConfig config, string assetPath)
|
|
||||||
{
|
|
||||||
EnsurePoolContainer();
|
|
||||||
|
|
||||||
string poolKey = config.BuildResolvedPoolKey(assetPath);
|
|
||||||
if (_poolsByKey.TryGetValue(poolKey, out RuntimePrefabPool existingPool))
|
|
||||||
{
|
|
||||||
return existingPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pool = MemoryPool.Acquire<RuntimePrefabPool>();
|
|
||||||
pool.Initialize(
|
|
||||||
config,
|
|
||||||
assetPath,
|
|
||||||
GetResourceLoader(config.resourceLoaderType),
|
|
||||||
this,
|
|
||||||
_shutdownTokenSource != null ? _shutdownTokenSource.Token : default);
|
|
||||||
|
|
||||||
_poolsByKey.Add(poolKey, pool);
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PerformCleanup()
|
|
||||||
{
|
|
||||||
float now = Time.time;
|
|
||||||
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
|
||||||
{
|
|
||||||
pool.TrimExpiredInstances(now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureDefaultResourceLoaders()
|
|
||||||
{
|
|
||||||
if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.AssetBundle))
|
|
||||||
{
|
|
||||||
_resourceLoaders[PoolResourceLoaderType.AssetBundle] = new AssetBundleResourceLoader();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.Resources))
|
|
||||||
{
|
|
||||||
_resourceLoaders[PoolResourceLoaderType.Resources] = new UnityResourcesLoader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsurePoolContainer()
|
|
||||||
{
|
|
||||||
if (poolContainer != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var container = new GameObject("GameObjectPoolContainer");
|
|
||||||
poolContainer = container.transform;
|
|
||||||
poolContainer.SetParent(transform, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadConfigs()
|
|
||||||
{
|
|
||||||
_configs.Clear();
|
|
||||||
_resolvedConfigCache.Clear();
|
|
||||||
_groupLoaderCache.Clear();
|
|
||||||
|
|
||||||
IResourceService resourceService = AppServices.Require<IResourceService>();
|
|
||||||
PoolConfigScriptableObject configAsset = resourceService.LoadAsset<PoolConfigScriptableObject>(poolConfigPath);
|
|
||||||
|
|
||||||
if (configAsset == null || configAsset.configs == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < configAsset.configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig config = configAsset.configs[i];
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Normalize();
|
|
||||||
if (string.IsNullOrWhiteSpace(config.assetPath))
|
|
||||||
{
|
|
||||||
Log.Warning($"PoolConfig at index {i} has an empty asset path and was ignored.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_configs.Add(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
_configs.Sort(PoolConfig.CompareByPriority);
|
|
||||||
LogConfigWarnings();
|
|
||||||
|
|
||||||
for (int i = 0; i < _configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig config = _configs[i];
|
|
||||||
if (!_groupLoaderCache.ContainsKey(config.group))
|
|
||||||
{
|
|
||||||
_groupLoaderCache[config.group] = config.resourceLoaderType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceService.UnloadAsset(configAsset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask PrewarmConfiguredPoolsAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig config = _configs[i];
|
|
||||||
if (!config.preloadOnInitialize || config.prewarmCount <= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.matchMode != PoolMatchMode.Exact)
|
|
||||||
{
|
|
||||||
Log.Warning(
|
|
||||||
$"PoolConfig '{config.assetPath}' uses Prefix mode and preloadOnInitialize. Prefix rules cannot infer a concrete asset to prewarm.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimePrefabPool pool = GetOrCreatePool(config, config.assetPath);
|
|
||||||
await pool.WarmupAsync(config.prewarmCount, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PoolConfig ResolveConfig(string assetPath, string group)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(assetPath))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasGroup = !string.IsNullOrEmpty(group);
|
|
||||||
string cacheKey = hasGroup ? string.Concat(group, "|", assetPath) : assetPath;
|
|
||||||
if (_resolvedConfigCache.TryGetValue(cacheKey, out PoolConfig cachedConfig))
|
|
||||||
{
|
|
||||||
return cachedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
PoolConfig matchedConfig = null;
|
|
||||||
for (int i = 0; i < _configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig candidate = _configs[i];
|
|
||||||
if (!candidate.Matches(assetPath, group))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedConfig == null)
|
|
||||||
{
|
|
||||||
matchedConfig = candidate;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool samePriority =
|
|
||||||
matchedConfig.matchMode == candidate.matchMode &&
|
|
||||||
matchedConfig.assetPath.Length == candidate.assetPath.Length;
|
|
||||||
|
|
||||||
if (samePriority)
|
|
||||||
{
|
|
||||||
Log.Warning(
|
|
||||||
$"Asset '{assetPath}' matched multiple pool configs with the same priority. Using '{matchedConfig.group}:{matchedConfig.assetPath}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolvedConfigCache[cacheKey] = matchedConfig;
|
|
||||||
return matchedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameObject LoadUnpooled(string assetPath, string group, Transform parent)
|
|
||||||
{
|
|
||||||
IResourceLoader loader = GetDirectLoadResourceLoader(group);
|
|
||||||
return loader.LoadGameObject(assetPath, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask<GameObject> LoadUnpooledAsync(
|
|
||||||
string assetPath,
|
|
||||||
string group,
|
|
||||||
Transform parent,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
IResourceLoader loader = GetDirectLoadResourceLoader(group);
|
|
||||||
return await loader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IResourceLoader GetDirectLoadResourceLoader(string group)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(group) &&
|
|
||||||
_groupLoaderCache.TryGetValue(group, out PoolResourceLoaderType loaderType))
|
|
||||||
{
|
|
||||||
return GetResourceLoader(loaderType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetResourceLoader(DefaultDirectLoadResourceLoaderType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IResourceLoader GetResourceLoader(PoolResourceLoaderType loaderType)
|
|
||||||
{
|
|
||||||
if (_resourceLoaders.TryGetValue(loaderType, out IResourceLoader loader) && loader != null)
|
|
||||||
{
|
|
||||||
return loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"Resource loader not registered: {loaderType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureReadyForSyncUse()
|
|
||||||
{
|
|
||||||
if (!_initializationCompleted)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"GameObjectPool is still initializing. Use the async APIs or wait until initialization completes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_initializationException != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("GameObjectPool initialization failed.", _initializationException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask EnsureInitializedAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await _initializeTask.AttachExternalCancellation(cancellationToken);
|
|
||||||
|
|
||||||
if (_initializationException != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("GameObjectPool initialization failed.", _initializationException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogConfigWarnings()
|
|
||||||
{
|
|
||||||
var seen = new HashSet<(string group, string assetPath)>();
|
|
||||||
for (int i = 0; i < _configs.Count; i++)
|
|
||||||
{
|
|
||||||
PoolConfig config = _configs[i];
|
|
||||||
var key = (config.group, config.assetPath);
|
|
||||||
if (!seen.Add(key))
|
|
||||||
{
|
|
||||||
Log.Warning($"Duplicate pool config detected: '{config.group}:{config.assetPath}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReleaseDebugSnapshots()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _debugSnapshots.Count; i++)
|
|
||||||
{
|
|
||||||
MemoryPool.Release(_debugSnapshots[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_debugSnapshots.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 17b192955f7691b4e8283328c926ab1a
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using AlicizaX.Resource.Runtime;
|
|
||||||
using Cysharp.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
public interface IResourceLoader
|
|
||||||
{
|
|
||||||
GameObject LoadPrefab(string location);
|
|
||||||
UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default);
|
|
||||||
GameObject LoadGameObject(string location, Transform parent = null);
|
|
||||||
UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default);
|
|
||||||
void UnloadAsset(GameObject gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UnityResourcesLoader : IResourceLoader
|
|
||||||
{
|
|
||||||
public GameObject LoadPrefab(string location)
|
|
||||||
{
|
|
||||||
return Resources.Load<GameObject>(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await Resources.LoadAsync<GameObject>(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameObject LoadGameObject(string location, Transform parent = null)
|
|
||||||
{
|
|
||||||
var prefab = Resources.Load<GameObject>(location);
|
|
||||||
if (prefab == null) return null;
|
|
||||||
|
|
||||||
var instance = GameObject.Instantiate(prefab);
|
|
||||||
if (instance != null && parent != null)
|
|
||||||
{
|
|
||||||
instance.transform.SetParent(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var prefab = await Resources.LoadAsync<GameObject>(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
|
|
||||||
if (prefab == null) return null;
|
|
||||||
|
|
||||||
var instance = GameObject.Instantiate(prefab);
|
|
||||||
if (instance != null && parent != null)
|
|
||||||
{
|
|
||||||
instance.transform.SetParent(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnloadAsset(GameObject gameObject)
|
|
||||||
{
|
|
||||||
// Resources.UnloadAsset cannot unload GameObjects.
|
|
||||||
// The prefab reference is nulled by the caller; Unity handles cleanup via UnloadUnusedAssets.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AssetBundleResourceLoader : IResourceLoader
|
|
||||||
{
|
|
||||||
private IResourceService _resourceService;
|
|
||||||
|
|
||||||
IResourceService ResourceService
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_resourceService == null)
|
|
||||||
{
|
|
||||||
_resourceService = AppServices.Require<IResourceService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _resourceService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public GameObject LoadPrefab(string location)
|
|
||||||
{
|
|
||||||
return ResourceService.LoadAsset<GameObject>(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await ResourceService.LoadAssetAsync<GameObject>(location, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameObject LoadGameObject(string location, Transform parent = null)
|
|
||||||
{
|
|
||||||
return ResourceService.LoadGameObject(location, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await ResourceService.LoadGameObjectAsync(location, parent, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnloadAsset(GameObject gameObject)
|
|
||||||
{
|
|
||||||
ResourceService.UnloadAsset(gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 9b48a0c8ea0e494f91746c5c40670feb
|
|
||||||
timeCreated: 1774439792
|
|
||||||
12
Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs
Normal file
12
Runtime/ABase/GameObjectPool/PoolConfigScriptableObject.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "PoolConfig", menuName = "GameplaySystem/PoolConfig", order = 10)]
|
||||||
|
public class PoolConfigScriptableObject : ScriptableObject
|
||||||
|
{
|
||||||
|
public List<PoolConfig> configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,573 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using Cysharp.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public sealed class GameObjectPoolInstanceSnapshot : IMemory
|
|
||||||
{
|
|
||||||
public string instanceName;
|
|
||||||
public bool isActive;
|
|
||||||
public float idleDuration;
|
|
||||||
public GameObject gameObject;
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
instanceName = null;
|
|
||||||
isActive = false;
|
|
||||||
idleDuration = 0f;
|
|
||||||
gameObject = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class GameObjectPoolSnapshot : IMemory
|
|
||||||
{
|
|
||||||
public string group;
|
|
||||||
public string assetPath;
|
|
||||||
public PoolMatchMode matchMode;
|
|
||||||
public PoolResourceLoaderType loaderType;
|
|
||||||
public int capacity;
|
|
||||||
public int totalCount;
|
|
||||||
public int activeCount;
|
|
||||||
public int inactiveCount;
|
|
||||||
public float instanceIdleTimeout;
|
|
||||||
public float prefabUnloadDelay;
|
|
||||||
public bool prefabLoaded;
|
|
||||||
public float prefabIdleDuration;
|
|
||||||
public List<GameObjectPoolInstanceSnapshot> instances = new List<GameObjectPoolInstanceSnapshot>();
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
group = null;
|
|
||||||
assetPath = null;
|
|
||||||
matchMode = default;
|
|
||||||
loaderType = default;
|
|
||||||
capacity = 0;
|
|
||||||
totalCount = 0;
|
|
||||||
activeCount = 0;
|
|
||||||
inactiveCount = 0;
|
|
||||||
instanceIdleTimeout = 0f;
|
|
||||||
prefabUnloadDelay = 0f;
|
|
||||||
prefabLoaded = false;
|
|
||||||
prefabIdleDuration = 0f;
|
|
||||||
|
|
||||||
for (int i = 0; i < instances.Count; i++)
|
|
||||||
{
|
|
||||||
MemoryPool.Release(instances[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
instances.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
public sealed class PooledGameObjectMarker : MonoBehaviour
|
|
||||||
{
|
|
||||||
private RuntimePrefabPool _owner;
|
|
||||||
private RuntimePooledInstance _instance;
|
|
||||||
|
|
||||||
internal void Bind(RuntimePrefabPool owner, RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
_instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Detach()
|
|
||||||
{
|
|
||||||
_owner = null;
|
|
||||||
_instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
if (_owner != null && _instance != null)
|
|
||||||
{
|
|
||||||
_owner.NotifyInstanceDestroyed(_instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class RuntimePooledInstance : IMemory
|
|
||||||
{
|
|
||||||
public GameObject GameObject { get; private set; }
|
|
||||||
public PooledGameObjectMarker Marker { get; private set; }
|
|
||||||
public LinkedListNode<RuntimePooledInstance> InactiveNode { get; set; }
|
|
||||||
public bool IsActive { get; set; }
|
|
||||||
public float LastReleaseTime { get; set; }
|
|
||||||
|
|
||||||
public void Initialize(GameObject gameObject, PooledGameObjectMarker marker)
|
|
||||||
{
|
|
||||||
GameObject = gameObject;
|
|
||||||
Marker = marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
GameObject = null;
|
|
||||||
Marker = null;
|
|
||||||
InactiveNode = null;
|
|
||||||
IsActive = false;
|
|
||||||
LastReleaseTime = 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class RuntimePrefabPool : IMemory
|
|
||||||
{
|
|
||||||
private static readonly Comparison<GameObjectPoolInstanceSnapshot> InstanceSnapshotComparer = (left, right) =>
|
|
||||||
{
|
|
||||||
if (left == null && right == null) return 0;
|
|
||||||
if (left == null) return 1;
|
|
||||||
if (right == null) return -1;
|
|
||||||
if (left.isActive != right.isActive) return left.isActive ? -1 : 1;
|
|
||||||
return string.Compare(left.instanceName, right.instanceName, StringComparison.Ordinal);
|
|
||||||
};
|
|
||||||
private PoolConfig _config;
|
|
||||||
private string _assetPath;
|
|
||||||
private IResourceLoader _loader;
|
|
||||||
private GameObjectPoolManager _service;
|
|
||||||
private CancellationToken _shutdownToken;
|
|
||||||
private Dictionary<GameObject, RuntimePooledInstance> _instancesByObject;
|
|
||||||
private LinkedList<RuntimePooledInstance> _inactiveInstances;
|
|
||||||
private List<RuntimePooledInstance> _shutdownBuffer;
|
|
||||||
private Transform _root;
|
|
||||||
|
|
||||||
private GameObject _prefab;
|
|
||||||
private UniTaskCompletionSource<GameObject> _prefabLoadSource;
|
|
||||||
private int _activeCount;
|
|
||||||
private float _lastPrefabTouchTime;
|
|
||||||
|
|
||||||
public RuntimePrefabPool()
|
|
||||||
{
|
|
||||||
_instancesByObject = new Dictionary<GameObject, RuntimePooledInstance>();
|
|
||||||
_inactiveInstances = new LinkedList<RuntimePooledInstance>();
|
|
||||||
_shutdownBuffer = new List<RuntimePooledInstance>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize(
|
|
||||||
PoolConfig config,
|
|
||||||
string assetPath,
|
|
||||||
IResourceLoader loader,
|
|
||||||
GameObjectPoolManager service,
|
|
||||||
CancellationToken shutdownToken)
|
|
||||||
{
|
|
||||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
|
||||||
_assetPath = assetPath ?? throw new ArgumentNullException(nameof(assetPath));
|
|
||||||
_loader = loader ?? throw new ArgumentNullException(nameof(loader));
|
|
||||||
_service = service ?? throw new ArgumentNullException(nameof(service));
|
|
||||||
_shutdownToken = shutdownToken;
|
|
||||||
_lastPrefabTouchTime = Time.time;
|
|
||||||
|
|
||||||
_instancesByObject.Clear();
|
|
||||||
_inactiveInstances.Clear();
|
|
||||||
_prefab = null;
|
|
||||||
_prefabLoadSource = null;
|
|
||||||
_activeCount = 0;
|
|
||||||
|
|
||||||
GameObject rootObject = new GameObject($"Pool_{SanitizeName(config.group)}_{SanitizeName(assetPath)}");
|
|
||||||
_root = rootObject.transform;
|
|
||||||
_root.SetParent(service.poolContainer, false);
|
|
||||||
rootObject.SetActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AssetPath => _assetPath;
|
|
||||||
public PoolConfig Config => _config;
|
|
||||||
public int TotalCount => _instancesByObject.Count;
|
|
||||||
public int ActiveCount => _activeCount;
|
|
||||||
public int InactiveCount => _inactiveInstances.Count;
|
|
||||||
public bool IsPrefabLoaded => _prefab != null;
|
|
||||||
public float PrefabIdleDuration => _prefab == null ? 0f : Mathf.Max(0f, Time.time - _lastPrefabTouchTime);
|
|
||||||
|
|
||||||
public GameObject Acquire(Transform parent)
|
|
||||||
{
|
|
||||||
EnsurePrefabLoaded();
|
|
||||||
return AcquirePrepared(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask<GameObject> AcquireAsync(Transform parent, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await EnsurePrefabLoadedAsync(cancellationToken);
|
|
||||||
return AcquirePrepared(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Warmup(int count)
|
|
||||||
{
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsurePrefabLoaded();
|
|
||||||
WarmupPrepared(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async UniTask WarmupAsync(int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await EnsurePrefabLoadedAsync(cancellationToken);
|
|
||||||
WarmupPrepared(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Release(GameObject gameObject)
|
|
||||||
{
|
|
||||||
if (gameObject == null || !_instancesByObject.TryGetValue(gameObject, out RuntimePooledInstance instance))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance.IsActive)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.IsActive = false;
|
|
||||||
instance.LastReleaseTime = Time.time;
|
|
||||||
_activeCount = Mathf.Max(0, _activeCount - 1);
|
|
||||||
|
|
||||||
gameObject.SetActive(false);
|
|
||||||
|
|
||||||
Transform transform = gameObject.transform;
|
|
||||||
transform.SetParent(_root, false);
|
|
||||||
transform.localPosition = Vector3.zero;
|
|
||||||
transform.localRotation = Quaternion.identity;
|
|
||||||
transform.localScale = Vector3.one;
|
|
||||||
|
|
||||||
instance.InactiveNode = _inactiveInstances.AddLast(instance);
|
|
||||||
TouchPrefab();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NotifyInstanceDestroyed(RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveInstance(instance);
|
|
||||||
MemoryPool.Release(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TrimExpiredInstances(float now)
|
|
||||||
{
|
|
||||||
if (_config.instanceIdleTimeout > 0f)
|
|
||||||
{
|
|
||||||
while (_inactiveInstances.First != null)
|
|
||||||
{
|
|
||||||
RuntimePooledInstance candidate = _inactiveInstances.First.Value;
|
|
||||||
if (now - candidate.LastReleaseTime < _config.instanceIdleTimeout)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyInstance(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_prefab != null &&
|
|
||||||
_instancesByObject.Count == 0 &&
|
|
||||||
_config.prefabUnloadDelay > 0f &&
|
|
||||||
now - _lastPrefabTouchTime >= _config.prefabUnloadDelay)
|
|
||||||
{
|
|
||||||
_loader.UnloadAsset(_prefab);
|
|
||||||
_prefab = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameObjectPoolSnapshot CreateSnapshot()
|
|
||||||
{
|
|
||||||
var snapshot = MemoryPool.Acquire<GameObjectPoolSnapshot>();
|
|
||||||
snapshot.group = _config.group;
|
|
||||||
snapshot.assetPath = _assetPath;
|
|
||||||
snapshot.matchMode = _config.matchMode;
|
|
||||||
snapshot.loaderType = _config.resourceLoaderType;
|
|
||||||
snapshot.capacity = _config.capacity;
|
|
||||||
snapshot.totalCount = _instancesByObject.Count;
|
|
||||||
snapshot.activeCount = _activeCount;
|
|
||||||
snapshot.inactiveCount = _inactiveInstances.Count;
|
|
||||||
snapshot.instanceIdleTimeout = _config.instanceIdleTimeout;
|
|
||||||
snapshot.prefabUnloadDelay = _config.prefabUnloadDelay;
|
|
||||||
snapshot.prefabLoaded = _prefab != null;
|
|
||||||
snapshot.prefabIdleDuration = PrefabIdleDuration;
|
|
||||||
|
|
||||||
foreach (RuntimePooledInstance instance in _instancesByObject.Values)
|
|
||||||
{
|
|
||||||
var instanceSnapshot = MemoryPool.Acquire<GameObjectPoolInstanceSnapshot>();
|
|
||||||
instanceSnapshot.instanceName = instance.GameObject != null ? instance.GameObject.name : "<destroyed>";
|
|
||||||
instanceSnapshot.isActive = instance.IsActive;
|
|
||||||
instanceSnapshot.idleDuration = instance.IsActive ? 0f : Mathf.Max(0f, Time.time - instance.LastReleaseTime);
|
|
||||||
instanceSnapshot.gameObject = instance.GameObject;
|
|
||||||
snapshot.instances.Add(instanceSnapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.instances.Sort(InstanceSnapshotComparer);
|
|
||||||
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shutdown()
|
|
||||||
{
|
|
||||||
_shutdownBuffer.AddRange(_instancesByObject.Values);
|
|
||||||
foreach (RuntimePooledInstance instance in _shutdownBuffer)
|
|
||||||
{
|
|
||||||
DestroyInstance(instance);
|
|
||||||
}
|
|
||||||
_shutdownBuffer.Clear();
|
|
||||||
|
|
||||||
_inactiveInstances.Clear();
|
|
||||||
_instancesByObject.Clear();
|
|
||||||
_activeCount = 0;
|
|
||||||
|
|
||||||
if (_prefab != null)
|
|
||||||
{
|
|
||||||
_loader.UnloadAsset(_prefab);
|
|
||||||
_prefab = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_root != null)
|
|
||||||
{
|
|
||||||
GameObject.Destroy(_root.gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
_root = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsurePrefabLoaded()
|
|
||||||
{
|
|
||||||
if (_prefab != null)
|
|
||||||
{
|
|
||||||
TouchPrefab();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_prefabLoadSource != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Pool asset '{_assetPath}' is being loaded asynchronously. Use the async acquire API.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_prefab = _loader.LoadPrefab(_assetPath);
|
|
||||||
if (_prefab == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to load pooled prefab '{_assetPath}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
TouchPrefab();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async UniTask EnsurePrefabLoadedAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_prefab != null)
|
|
||||||
{
|
|
||||||
TouchPrefab();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_prefabLoadSource != null)
|
|
||||||
{
|
|
||||||
await _prefabLoadSource.Task.AttachExternalCancellation(cancellationToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_prefabLoadSource = new UniTaskCompletionSource<GameObject>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
GameObject prefab = await _loader.LoadPrefabAsync(_assetPath, _shutdownToken);
|
|
||||||
if (prefab == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to load pooled prefab '{_assetPath}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_prefab = prefab;
|
|
||||||
TouchPrefab();
|
|
||||||
_prefabLoadSource.TrySetResult(prefab);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_prefabLoadSource.TrySetException(exception);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_prefabLoadSource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WarmupPrepared(int requestedCount)
|
|
||||||
{
|
|
||||||
int targetCount = Mathf.Clamp(requestedCount, 0, _config.capacity);
|
|
||||||
if (targetCount <= _instancesByObject.Count)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int toCreate = targetCount - _instancesByObject.Count;
|
|
||||||
for (int i = 0; i < toCreate; i++)
|
|
||||||
{
|
|
||||||
RuntimePooledInstance instance = CreateInstance();
|
|
||||||
ParkInactive(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameObject AcquirePrepared(Transform parent)
|
|
||||||
{
|
|
||||||
RuntimePooledInstance instance = null;
|
|
||||||
|
|
||||||
if (_inactiveInstances.Last != null)
|
|
||||||
{
|
|
||||||
instance = _inactiveInstances.Last.Value;
|
|
||||||
RemoveInactiveNode(instance);
|
|
||||||
}
|
|
||||||
else if (_instancesByObject.Count < _config.capacity)
|
|
||||||
{
|
|
||||||
instance = CreateInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
Log.Warning($"Pool exhausted for '{_assetPath}'. Capacity: {_config.capacity}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.IsActive = true;
|
|
||||||
_activeCount++;
|
|
||||||
TouchPrefab();
|
|
||||||
|
|
||||||
GameObject gameObject = instance.GameObject;
|
|
||||||
if (parent != null)
|
|
||||||
{
|
|
||||||
gameObject.transform.SetParent(parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameObject.SetActive(true);
|
|
||||||
return gameObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RuntimePooledInstance CreateInstance()
|
|
||||||
{
|
|
||||||
GameObject gameObject = GameObject.Instantiate(_prefab);
|
|
||||||
gameObject.SetActive(false);
|
|
||||||
gameObject.transform.SetParent(_root, false);
|
|
||||||
|
|
||||||
PooledGameObjectMarker marker = gameObject.GetComponent<PooledGameObjectMarker>();
|
|
||||||
if (marker == null)
|
|
||||||
{
|
|
||||||
marker = gameObject.AddComponent<PooledGameObjectMarker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var instance = MemoryPool.Acquire<RuntimePooledInstance>();
|
|
||||||
instance.Initialize(gameObject, marker);
|
|
||||||
marker.Bind(this, instance);
|
|
||||||
_instancesByObject.Add(gameObject, instance);
|
|
||||||
_service.RegisterOwnedObject(gameObject, this);
|
|
||||||
TouchPrefab();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParkInactive(RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.IsActive = false;
|
|
||||||
instance.LastReleaseTime = Time.time;
|
|
||||||
|
|
||||||
Transform transform = instance.GameObject.transform;
|
|
||||||
transform.SetParent(_root, false);
|
|
||||||
transform.localPosition = Vector3.zero;
|
|
||||||
transform.localRotation = Quaternion.identity;
|
|
||||||
transform.localScale = Vector3.one;
|
|
||||||
instance.GameObject.SetActive(false);
|
|
||||||
instance.InactiveNode = _inactiveInstances.AddLast(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DestroyInstance(RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveInstance(instance);
|
|
||||||
|
|
||||||
if (instance.GameObject != null)
|
|
||||||
{
|
|
||||||
instance.Marker?.Detach();
|
|
||||||
GameObject.Destroy(instance.GameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryPool.Release(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveInstance(RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
GameObject gameObject = instance?.GameObject;
|
|
||||||
if (gameObject == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveInactiveNode(instance);
|
|
||||||
|
|
||||||
if (instance.IsActive)
|
|
||||||
{
|
|
||||||
instance.IsActive = false;
|
|
||||||
_activeCount = Mathf.Max(0, _activeCount - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_instancesByObject.Remove(gameObject);
|
|
||||||
_service.UnregisterOwnedObject(gameObject);
|
|
||||||
TouchPrefab();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveInactiveNode(RuntimePooledInstance instance)
|
|
||||||
{
|
|
||||||
if (instance?.InactiveNode == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_inactiveInstances.Remove(instance.InactiveNode);
|
|
||||||
instance.InactiveNode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TouchPrefab()
|
|
||||||
{
|
|
||||||
_lastPrefabTouchTime = Time.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
_config = null;
|
|
||||||
_assetPath = null;
|
|
||||||
_loader = null;
|
|
||||||
_service = null;
|
|
||||||
_shutdownToken = default;
|
|
||||||
_instancesByObject.Clear();
|
|
||||||
_inactiveInstances.Clear();
|
|
||||||
_shutdownBuffer.Clear();
|
|
||||||
_root = null;
|
|
||||||
_prefab = null;
|
|
||||||
_prefabLoadSource = null;
|
|
||||||
_activeCount = 0;
|
|
||||||
_lastPrefabTouchTime = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string SanitizeName(string value)
|
|
||||||
{
|
|
||||||
return string.IsNullOrWhiteSpace(value)
|
|
||||||
? "Unnamed"
|
|
||||||
: value.Replace('/', '_').Replace('\\', '_').Replace(':', '_');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 542273e73f9fe8c46854d088f3c6387c
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -3,22 +3,21 @@ using AlicizaX.Debugger.Runtime;
|
|||||||
using AlicizaX.Localization.Runtime;
|
using AlicizaX.Localization.Runtime;
|
||||||
using AlicizaX.Resource.Runtime;
|
using AlicizaX.Resource.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Serialization;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
public class ModuleDynamicBind : MonoBehaviour
|
public class ModuleDynamicBind : MonoBehaviour
|
||||||
{
|
{
|
||||||
[FormerlySerializedAs("_resourceServiceBehaviour")] [SerializeField] private ResourceComponent resourceComponent;
|
[SerializeField] private ResourceComponent _resourceComponent;
|
||||||
[FormerlySerializedAs("_debuggerServiceBehaviour")] [SerializeField] private DebuggerComponent debuggerComponent;
|
[SerializeField] private DebuggerComponent _debuggerComponent;
|
||||||
[FormerlySerializedAs("_localizationServiceBehaviour")] [SerializeField] private LocalizationComponent localizationComponent;
|
[SerializeField] private LocalizationComponent _localizationComponent;
|
||||||
private ModuleDynamicBindInfo _dynamicBindInfo;
|
private ModuleDynamicBindInfo _dynamicBindInfo;
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
resourceComponent = GetComponentInChildren<ResourceComponent>();
|
_resourceComponent = GetComponentInChildren<ResourceComponent>();
|
||||||
debuggerComponent = GetComponentInChildren<DebuggerComponent>();
|
_debuggerComponent = GetComponentInChildren<DebuggerComponent>();
|
||||||
localizationComponent = GetComponentInChildren<LocalizationComponent>();
|
_localizationComponent = GetComponentInChildren<LocalizationComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
@ -27,20 +26,20 @@ namespace AlicizaX
|
|||||||
TextAsset text = Resources.Load<TextAsset>("ModuleDynamicBindInfo");
|
TextAsset text = Resources.Load<TextAsset>("ModuleDynamicBindInfo");
|
||||||
_dynamicBindInfo = Utility.Json.ToObject<ModuleDynamicBindInfo>(text.text);
|
_dynamicBindInfo = Utility.Json.ToObject<ModuleDynamicBindInfo>(text.text);
|
||||||
|
|
||||||
if (resourceComponent != null)
|
if (_resourceComponent != null)
|
||||||
{
|
{
|
||||||
resourceComponent.SetPlayMode(_dynamicBindInfo.ResMode);
|
_resourceComponent.SetPlayMode(_dynamicBindInfo.ResMode);
|
||||||
resourceComponent.SetDecryptionServices(_dynamicBindInfo.DecryptionServices);
|
_resourceComponent.SetDecryptionServices(_dynamicBindInfo.DecryptionServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debuggerComponent != null)
|
if (_debuggerComponent != null)
|
||||||
{
|
{
|
||||||
debuggerComponent.SetActiveMode(_dynamicBindInfo.DebuggerActiveWindowType);
|
_debuggerComponent.SetActiveMode(_dynamicBindInfo.DebuggerActiveWindowType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localizationComponent != null)
|
if (_localizationComponent != null)
|
||||||
{
|
{
|
||||||
localizationComponent.SetLanguage(_dynamicBindInfo.Language);
|
_localizationComponent.SetLanguage(_dynamicBindInfo.Language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对象池管理器。
|
/// 对象池管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IObjectPoolService : IService, IServiceTickable
|
public interface IObjectPoolModule: IModule, IModuleUpdate
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取对象池数量。
|
/// 获取对象池数量。
|
||||||
@ -8,24 +8,24 @@ namespace AlicizaX
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ObjectPoolComponent : MonoBehaviour
|
public sealed class ObjectPoolComponent : MonoBehaviour
|
||||||
{
|
{
|
||||||
private IObjectPoolService _mObjectPoolService = null;
|
private IObjectPoolModule _mObjectPoolModule = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取对象池数量。
|
/// 获取对象池数量。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get { return _mObjectPoolService.Count; }
|
get { return _mObjectPoolModule.Count; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_mObjectPoolService = AppServices.GetOrCreateScope<AppScope>().Register(new ObjectPoolService());
|
_mObjectPoolModule = ModuleSystem.RegisterModule<IObjectPoolModule,ObjectPoolModule>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
_mObjectPoolService = null;
|
_mObjectPoolModule = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -35,7 +35,7 @@ namespace AlicizaX
|
|||||||
/// <returns>所有对象池。</returns>
|
/// <returns>所有对象池。</returns>
|
||||||
public ObjectPoolBase[] GetAllObjectPools(bool sort)
|
public ObjectPoolBase[] GetAllObjectPools(bool sort)
|
||||||
{
|
{
|
||||||
return _mObjectPoolService.GetAllObjectPools(sort);
|
return _mObjectPoolModule.GetAllObjectPools(sort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
|
||||||
namespace AlicizaX.ObjectPool
|
namespace AlicizaX.ObjectPool
|
||||||
{
|
{
|
||||||
internal sealed partial class ObjectPoolService : IObjectPoolService
|
internal sealed partial class ObjectPoolModule : IObjectPoolModule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 内部对象。
|
/// 内部对象。
|
||||||
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
|
||||||
namespace AlicizaX.ObjectPool
|
namespace AlicizaX.ObjectPool
|
||||||
{
|
{
|
||||||
internal sealed partial class ObjectPoolService : IObjectPoolService
|
internal sealed partial class ObjectPoolModule : IObjectPoolModule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对象池。
|
/// 对象池。
|
||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.ObjectPool
|
namespace AlicizaX.ObjectPool
|
||||||
{
|
{
|
||||||
@ -9,7 +8,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
/// 对象池管理器。
|
/// 对象池管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UnityEngine.Scripting.Preserve]
|
[UnityEngine.Scripting.Preserve]
|
||||||
internal sealed partial class ObjectPoolService : ServiceBase, IObjectPoolService
|
internal sealed partial class ObjectPoolModule : IObjectPoolModule
|
||||||
{
|
{
|
||||||
private const int DefaultCapacity = int.MaxValue;
|
private const int DefaultCapacity = int.MaxValue;
|
||||||
private const float DefaultExpireTime = float.MaxValue;
|
private const float DefaultExpireTime = float.MaxValue;
|
||||||
@ -22,7 +21,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化对象池管理器的新实例。
|
/// 初始化对象池管理器的新实例。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ObjectPoolService()
|
public ObjectPoolModule()
|
||||||
{
|
{
|
||||||
m_ObjectPools = new Dictionary<TypeNamePair, ObjectPoolBase>();
|
m_ObjectPools = new Dictionary<TypeNamePair, ObjectPoolBase>();
|
||||||
m_CachedAllObjectPools = new List<ObjectPoolBase>();
|
m_CachedAllObjectPools = new List<ObjectPoolBase>();
|
||||||
@ -48,20 +47,18 @@ namespace AlicizaX.ObjectPool
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||||
void IServiceTickable.Tick(float deltaTime)
|
void IModuleUpdate.Update(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<TypeNamePair, ObjectPoolBase> objectPool in m_ObjectPools)
|
foreach (KeyValuePair<TypeNamePair, ObjectPoolBase> objectPool in m_ObjectPools)
|
||||||
{
|
{
|
||||||
objectPool.Value.Update(Time.deltaTime,Time.unscaledDeltaTime);
|
objectPool.Value.Update(elapseSeconds, realElapseSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 关闭并清理对象池管理器。
|
/// 关闭并清理对象池管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnInitialize() { }
|
void IModule.Dispose()
|
||||||
|
|
||||||
protected override void OnDestroyService()
|
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<TypeNamePair, ObjectPoolBase> objectPool in m_ObjectPools)
|
foreach (KeyValuePair<TypeNamePair, ObjectPoolBase> objectPool in m_ObjectPools)
|
||||||
{
|
{
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using AlicizaX.Resource.Runtime;
|
using AlicizaX.Resource.Runtime;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Audio;
|
using UnityEngine.Audio;
|
||||||
@ -14,8 +14,8 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
private int _instanceId;
|
private int _instanceId;
|
||||||
private AudioSource _source;
|
private AudioSource _source;
|
||||||
private AudioData _audioData;
|
private AudioData _audioData;
|
||||||
private IAudioService _audioService;
|
private IAudioModule _audioModule;
|
||||||
private IResourceService _resourceService;
|
private IResourceModule _resourceModule;
|
||||||
private Transform _transform;
|
private Transform _transform;
|
||||||
private float _volume = 1.0f;
|
private float _volume = 1.0f;
|
||||||
private float _duration;
|
private float _duration;
|
||||||
@ -237,8 +237,8 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
/// <param name="index">音频代理辅助器编号。</param>
|
/// <param name="index">音频代理辅助器编号。</param>
|
||||||
public void Init(AudioCategory audioCategory, int index = 0)
|
public void Init(AudioCategory audioCategory, int index = 0)
|
||||||
{
|
{
|
||||||
_audioService = AppServices.Require<IAudioService>();
|
_audioModule = ModuleSystem.GetModule<IAudioModule>();
|
||||||
_resourceService = AppServices.Require<IResourceService>();
|
_resourceModule = ModuleSystem.GetModule<IResourceModule>();
|
||||||
GameObject host = new GameObject(Utility.Text.Format("Audio Agent Helper - {0} - {1}", audioCategory.AudioMixerGroup.name, index));
|
GameObject host = new GameObject(Utility.Text.Format("Audio Agent Helper - {0} - {1}", audioCategory.AudioMixerGroup.name, index));
|
||||||
host.transform.SetParent(audioCategory.InstanceRoot);
|
host.transform.SetParent(audioCategory.InstanceRoot);
|
||||||
host.transform.localPosition = Vector3.zero;
|
host.transform.localPosition = Vector3.zero;
|
||||||
@ -269,7 +269,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
_duration = 0;
|
_duration = 0;
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
if (bInPool && _audioService.AudioClipPool.TryGetValue(path, out var operationHandle))
|
if (bInPool && _audioModule.AudioClipPool.TryGetValue(path, out var operationHandle))
|
||||||
{
|
{
|
||||||
OnAssetLoadComplete(operationHandle);
|
OnAssetLoadComplete(operationHandle);
|
||||||
return;
|
return;
|
||||||
@ -278,12 +278,12 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
if (bAsync)
|
if (bAsync)
|
||||||
{
|
{
|
||||||
_audioAgentRuntimeState = AudioAgentRuntimeState.Loading;
|
_audioAgentRuntimeState = AudioAgentRuntimeState.Loading;
|
||||||
AssetHandle handle = _resourceService.LoadAssetAsyncHandle<AudioClip>(path);
|
AssetHandle handle = _resourceModule.LoadAssetAsyncHandle<AudioClip>(path);
|
||||||
handle.Completed += OnAssetLoadComplete;
|
handle.Completed += OnAssetLoadComplete;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AssetHandle handle = _resourceService.LoadAssetSyncHandle<AudioClip>(path);
|
AssetHandle handle = _resourceModule.LoadAssetSyncHandle<AudioClip>(path);
|
||||||
OnAssetLoadComplete(handle);
|
OnAssetLoadComplete(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -352,7 +352,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
{
|
{
|
||||||
if (_inPool)
|
if (_inPool)
|
||||||
{
|
{
|
||||||
_audioService.AudioClipPool.TryAdd(handle.GetAssetInfo().Address, handle);
|
_audioModule.AudioClipPool.TryAdd(handle.GetAssetInfo().Address, handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Audio;
|
using UnityEngine.Audio;
|
||||||
@ -72,7 +72,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
/// <param name="audioGroupConfig">音频轨道组配置。</param>
|
/// <param name="audioGroupConfig">音频轨道组配置。</param>
|
||||||
public AudioCategory(int maxChannel, AudioMixer audioMixer, AudioGroupConfig audioGroupConfig)
|
public AudioCategory(int maxChannel, AudioMixer audioMixer, AudioGroupConfig audioGroupConfig)
|
||||||
{
|
{
|
||||||
var audioModule = AppServices.Require<IAudioService>();
|
var audioModule = ModuleSystem.GetModule<IAudioModule>();
|
||||||
|
|
||||||
this.audioMixer = audioMixer;
|
this.audioMixer = audioMixer;
|
||||||
_maxChannel = maxChannel;
|
_maxChannel = maxChannel;
|
||||||
|
|||||||
@ -18,11 +18,11 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
|
|
||||||
[SerializeField] private AudioGroupConfig[] m_AudioGroupConfigs = null;
|
[SerializeField] private AudioGroupConfig[] m_AudioGroupConfigs = null;
|
||||||
|
|
||||||
private IAudioService _audioService;
|
private IAudioModule _audioModule;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_audioService = AppServices.GetOrCreateScope<AppScope>().Register(new AudioService());
|
_audioModule = ModuleSystem.RegisterModule<IAudioModule,AudioModule>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -32,7 +32,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
{
|
{
|
||||||
if (m_InstanceRoot == null)
|
if (m_InstanceRoot == null)
|
||||||
{
|
{
|
||||||
m_InstanceRoot = new GameObject("[AudioService Instances]").transform;
|
m_InstanceRoot = new GameObject("[AudioModule Instances]").transform;
|
||||||
m_InstanceRoot.SetParent(gameObject.transform);
|
m_InstanceRoot.SetParent(gameObject.transform);
|
||||||
m_InstanceRoot.localScale = Vector3.one;
|
m_InstanceRoot.localScale = Vector3.one;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
m_AudioMixer = Resources.Load<AudioMixer>("AudioMixer");
|
m_AudioMixer = Resources.Load<AudioMixer>("AudioMixer");
|
||||||
}
|
}
|
||||||
|
|
||||||
_audioService.Initialize(m_AudioGroupConfigs, m_InstanceRoot, m_AudioMixer);
|
_audioModule.Initialize(m_AudioGroupConfigs, m_InstanceRoot, m_AudioMixer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -12,7 +12,7 @@ using AudioType = AlicizaX.Audio.Runtime.AudioType;
|
|||||||
|
|
||||||
namespace AlicizaX.Audio.Runtime
|
namespace AlicizaX.Audio.Runtime
|
||||||
{
|
{
|
||||||
internal class AudioService : ServiceBase, IAudioService
|
internal class AudioModule : IAudioModule
|
||||||
{
|
{
|
||||||
public const string MUSIC_VOLUME_NAME = "MusicVolume";
|
public const string MUSIC_VOLUME_NAME = "MusicVolume";
|
||||||
public const string UI_SOUND_VOLUME_NAME = "UISoundVolume";
|
public const string UI_SOUND_VOLUME_NAME = "UISoundVolume";
|
||||||
@ -321,12 +321,10 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private IResourceService _resourceService;
|
private IResourceModule _resourceModule;
|
||||||
|
|
||||||
|
|
||||||
protected override void OnInitialize() { }
|
void IModule.Dispose()
|
||||||
|
|
||||||
protected override void OnDestroyService()
|
|
||||||
{
|
{
|
||||||
StopAll(fadeout: false);
|
StopAll(fadeout: false);
|
||||||
CleanSoundPool();
|
CleanSoundPool();
|
||||||
@ -341,7 +339,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
/// <exception cref="GameFrameworkException"></exception>
|
/// <exception cref="GameFrameworkException"></exception>
|
||||||
public void Initialize(AudioGroupConfig[] audioGroupConfigs, Transform instanceRoot = null, AudioMixer audioMixer = null)
|
public void Initialize(AudioGroupConfig[] audioGroupConfigs, Transform instanceRoot = null, AudioMixer audioMixer = null)
|
||||||
{
|
{
|
||||||
_resourceService = AppServices.Require<IResourceService>();
|
_resourceModule = ModuleSystem.GetModule<IResourceModule>();
|
||||||
if (_instanceRoot == null)
|
if (_instanceRoot == null)
|
||||||
{
|
{
|
||||||
_instanceRoot = instanceRoot;
|
_instanceRoot = instanceRoot;
|
||||||
@ -528,7 +526,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
{
|
{
|
||||||
if (AudioClipPool != null && !AudioClipPool.ContainsKey(path))
|
if (AudioClipPool != null && !AudioClipPool.ContainsKey(path))
|
||||||
{
|
{
|
||||||
AssetHandle assetData = _resourceService.LoadAssetAsyncHandle<AudioClip>(path);
|
AssetHandle assetData = _resourceModule.LoadAssetAsyncHandle<AudioClip>(path);
|
||||||
assetData.Completed += handle => { AudioClipPool?.Add(path, handle); };
|
assetData.Completed += handle => { AudioClipPool?.Add(path, handle); };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,19 +576,18 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||||
void IServiceTickable.Tick(float deltaTime)
|
void IModuleUpdate.Update(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
foreach (var audioCategory in _audioCategories)
|
foreach (var audioCategory in _audioCategories)
|
||||||
{
|
{
|
||||||
if (audioCategory != null)
|
if (audioCategory != null)
|
||||||
{
|
{
|
||||||
audioCategory.Update(deltaTime);
|
audioCategory.Update(elapseSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int Priority { get; }
|
public int Priority { get; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Audio;
|
using UnityEngine.Audio;
|
||||||
@ -6,7 +6,7 @@ using YooAsset;
|
|||||||
|
|
||||||
namespace AlicizaX.Audio.Runtime
|
namespace AlicizaX.Audio.Runtime
|
||||||
{
|
{
|
||||||
public interface IAudioService:IService, IServiceTickable
|
public interface IAudioModule:IModule,IModuleUpdate
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 总音量控制。
|
/// 总音量控制。
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using AlicizaX.ObjectPool;
|
using AlicizaX.ObjectPool;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AlicizaX.Debugger.Runtime
|
namespace AlicizaX.Debugger.Runtime
|
||||||
@ -7,11 +7,16 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
{
|
{
|
||||||
private sealed class ObjectPoolInformationWindow : ScrollableDebuggerWindowBase
|
private sealed class ObjectPoolInformationWindow : ScrollableDebuggerWindowBase
|
||||||
{
|
{
|
||||||
private IObjectPoolService m_ObjectPoolService = null;
|
private IObjectPoolModule m_ObjectPoolComponent = null;
|
||||||
|
|
||||||
public override void Initialize(params object[] args)
|
public override void Initialize(params object[] args)
|
||||||
{
|
{
|
||||||
m_ObjectPoolService = AppServices.Require<IObjectPoolService>();
|
m_ObjectPoolComponent = ModuleSystem.GetModule<IObjectPoolModule>();
|
||||||
|
if (m_ObjectPoolComponent == null)
|
||||||
|
{
|
||||||
|
Log.Error("Object pool component is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDrawScrollableWindow()
|
protected override void OnDrawScrollableWindow()
|
||||||
@ -19,10 +24,10 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
GUILayout.Label("<b>Object Pool Information</b>");
|
GUILayout.Label("<b>Object Pool Information</b>");
|
||||||
GUILayout.BeginVertical("box");
|
GUILayout.BeginVertical("box");
|
||||||
{
|
{
|
||||||
DrawItem("Object Pool Count", m_ObjectPoolService.Count.ToString());
|
DrawItem("Object Pool Count", m_ObjectPoolComponent.Count.ToString());
|
||||||
}
|
}
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
ObjectPoolBase[] objectPools = m_ObjectPoolService.GetAllObjectPools(true);
|
ObjectPoolBase[] objectPools = m_ObjectPoolComponent.GetAllObjectPools(true);
|
||||||
for (int i = 0; i < objectPools.Length; i++)
|
for (int i = 0; i < objectPools.Length; i++)
|
||||||
{
|
{
|
||||||
DrawObjectPool(objectPools[i]);
|
DrawObjectPool(objectPools[i]);
|
||||||
|
|||||||
67
Runtime/Debugger/DebuggerComponent.OperationsWindow.cs
Normal file
67
Runtime/Debugger/DebuggerComponent.OperationsWindow.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// //------------------------------------------------------------
|
||||||
|
// // Game Framework
|
||||||
|
// // Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||||
|
// // Homepage: https://gameframework.cn/
|
||||||
|
// // Feedback: mailto:ellan@gameframework.cn
|
||||||
|
// //------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// using GameFrameX.Runtime;
|
||||||
|
// using UnityEngine;
|
||||||
|
//
|
||||||
|
// namespace GameFrameX.Debugger.Runtime
|
||||||
|
// {
|
||||||
|
// public sealed partial class DebuggerComponent : GameFrameworkComponent
|
||||||
|
// {
|
||||||
|
// private sealed class OperationsWindow : ScrollableDebuggerWindowBase
|
||||||
|
// {
|
||||||
|
// protected override void OnDrawScrollableWindow()
|
||||||
|
// {
|
||||||
|
// GUILayout.Label("<b>Operations</b>");
|
||||||
|
// GUILayout.BeginVertical("box");
|
||||||
|
// {
|
||||||
|
// ObjectPoolComponent objectPoolComponent = GameEntry.GetComponent<ObjectPoolComponent>();
|
||||||
|
// if (objectPoolComponent != null)
|
||||||
|
// {
|
||||||
|
// if (GUILayout.Button("Object Pool Release", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// objectPoolComponent.Release();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (GUILayout.Button("Object Pool Release All Unused", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// objectPoolComponent.ReleaseAllUnused();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ResourceComponent resourceCompoent = GameEntry.GetComponent<ResourceComponent>();
|
||||||
|
// if (resourceCompoent != null)
|
||||||
|
// {
|
||||||
|
// if (GUILayout.Button("Unload Unused Assets", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// resourceCompoent.ForceUnloadUnusedAssets(false);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (GUILayout.Button("Unload Unused Assets and Garbage Collect", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// resourceCompoent.ForceUnloadUnusedAssets(true);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (GUILayout.Button("Shutdown Game Framework (None)", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// GameEntry.Shutdown(ShutdownType.None);
|
||||||
|
// }
|
||||||
|
// if (GUILayout.Button("Shutdown Game Framework (Restart)", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// GameEntry.Shutdown(ShutdownType.Restart);
|
||||||
|
// }
|
||||||
|
// if (GUILayout.Button("Shutdown Game Framework (Quit)", GUILayout.Height(30f)))
|
||||||
|
// {
|
||||||
|
// GameEntry.Shutdown(ShutdownType.Quit);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// GUILayout.EndVertical();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 21b55582105cd3a44bfc4bfb935a35d1
|
guid: a695dd87b92d7374fbe3790f5a25e9d5
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -7,7 +7,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
{
|
{
|
||||||
private sealed class SettingsWindow : ScrollableDebuggerWindowBase
|
private sealed class SettingsWindow : ScrollableDebuggerWindowBase
|
||||||
{
|
{
|
||||||
private DebuggerComponent _mDebuggerComponent = null;
|
private DebuggerComponent m_DebuggerComponent = null;
|
||||||
private float m_LastIconX = 0f;
|
private float m_LastIconX = 0f;
|
||||||
private float m_LastIconY = 0f;
|
private float m_LastIconY = 0f;
|
||||||
private float m_LastWindowX = 0f;
|
private float m_LastWindowX = 0f;
|
||||||
@ -18,8 +18,8 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
public override void Initialize(params object[] args)
|
public override void Initialize(params object[] args)
|
||||||
{
|
{
|
||||||
_mDebuggerComponent = DebuggerComponent.Instance;
|
m_DebuggerComponent = DebuggerComponent.Instance;
|
||||||
if (_mDebuggerComponent == null)
|
if (m_DebuggerComponent == null)
|
||||||
{
|
{
|
||||||
Log.Error("Debugger component is invalid.");
|
Log.Error("Debugger component is invalid.");
|
||||||
return;
|
return;
|
||||||
@ -32,53 +32,53 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
m_LastWindowY = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Y", DefaultWindowRect.y);
|
m_LastWindowY = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Y", DefaultWindowRect.y);
|
||||||
m_LastWindowWidth = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Width", DefaultWindowRect.width);
|
m_LastWindowWidth = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Width", DefaultWindowRect.width);
|
||||||
m_LastWindowHeight = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Height", DefaultWindowRect.height);
|
m_LastWindowHeight = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Height", DefaultWindowRect.height);
|
||||||
_mDebuggerComponent.WindowScale = m_LastWindowScale = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Scale", DefaultWindowScale);
|
m_DebuggerComponent.WindowScale = m_LastWindowScale = Utility.PlayerPrefsX.GetFloat("Debugger.Window.Scale", DefaultWindowScale);
|
||||||
_mDebuggerComponent.IconRect = new Rect(m_LastIconX, m_LastIconY, DefaultIconRect.width, DefaultIconRect.height);
|
m_DebuggerComponent.IconRect = new Rect(m_LastIconX, m_LastIconY, DefaultIconRect.width, DefaultIconRect.height);
|
||||||
_mDebuggerComponent.WindowRect = new Rect(m_LastWindowX, m_LastWindowY, m_LastWindowWidth, m_LastWindowHeight);
|
m_DebuggerComponent.WindowRect = new Rect(m_LastWindowX, m_LastWindowY, m_LastWindowWidth, m_LastWindowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
if (m_LastIconX != _mDebuggerComponent.IconRect.x)
|
if (m_LastIconX != m_DebuggerComponent.IconRect.x)
|
||||||
{
|
{
|
||||||
m_LastIconX = _mDebuggerComponent.IconRect.x;
|
m_LastIconX = m_DebuggerComponent.IconRect.x;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Icon.X", _mDebuggerComponent.IconRect.x);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Icon.X", m_DebuggerComponent.IconRect.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastIconY != _mDebuggerComponent.IconRect.y)
|
if (m_LastIconY != m_DebuggerComponent.IconRect.y)
|
||||||
{
|
{
|
||||||
m_LastIconY = _mDebuggerComponent.IconRect.y;
|
m_LastIconY = m_DebuggerComponent.IconRect.y;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Icon.Y", _mDebuggerComponent.IconRect.y);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Icon.Y", m_DebuggerComponent.IconRect.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastWindowX != _mDebuggerComponent.WindowRect.x)
|
if (m_LastWindowX != m_DebuggerComponent.WindowRect.x)
|
||||||
{
|
{
|
||||||
m_LastWindowX = _mDebuggerComponent.WindowRect.x;
|
m_LastWindowX = m_DebuggerComponent.WindowRect.x;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Window.X", _mDebuggerComponent.WindowRect.x);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Window.X", m_DebuggerComponent.WindowRect.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastWindowY != _mDebuggerComponent.WindowRect.y)
|
if (m_LastWindowY != m_DebuggerComponent.WindowRect.y)
|
||||||
{
|
{
|
||||||
m_LastWindowY = _mDebuggerComponent.WindowRect.y;
|
m_LastWindowY = m_DebuggerComponent.WindowRect.y;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Y", _mDebuggerComponent.WindowRect.y);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Y", m_DebuggerComponent.WindowRect.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastWindowWidth != _mDebuggerComponent.WindowRect.width)
|
if (m_LastWindowWidth != m_DebuggerComponent.WindowRect.width)
|
||||||
{
|
{
|
||||||
m_LastWindowWidth = _mDebuggerComponent.WindowRect.width;
|
m_LastWindowWidth = m_DebuggerComponent.WindowRect.width;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Width", _mDebuggerComponent.WindowRect.width);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Width", m_DebuggerComponent.WindowRect.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastWindowHeight != _mDebuggerComponent.WindowRect.height)
|
if (m_LastWindowHeight != m_DebuggerComponent.WindowRect.height)
|
||||||
{
|
{
|
||||||
m_LastWindowHeight = _mDebuggerComponent.WindowRect.height;
|
m_LastWindowHeight = m_DebuggerComponent.WindowRect.height;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Height", _mDebuggerComponent.WindowRect.height);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Height", m_DebuggerComponent.WindowRect.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastWindowScale != _mDebuggerComponent.WindowScale)
|
if (m_LastWindowScale != m_DebuggerComponent.WindowScale)
|
||||||
{
|
{
|
||||||
m_LastWindowScale = _mDebuggerComponent.WindowScale;
|
m_LastWindowScale = m_DebuggerComponent.WindowScale;
|
||||||
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Scale", _mDebuggerComponent.WindowScale);
|
Utility.PlayerPrefsX.SetFloat("Debugger.Window.Scale", m_DebuggerComponent.WindowScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
{
|
{
|
||||||
float width = _mDebuggerComponent.WindowRect.width;
|
float width = m_DebuggerComponent.WindowRect.width;
|
||||||
GUILayout.Label("Width:", GUILayout.Width(60f));
|
GUILayout.Label("Width:", GUILayout.Width(60f));
|
||||||
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
||||||
{
|
{
|
||||||
@ -110,16 +110,16 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
width = Mathf.Clamp(width, 100f, Screen.width - 20f);
|
width = Mathf.Clamp(width, 100f, Screen.width - 20f);
|
||||||
if (width != _mDebuggerComponent.WindowRect.width)
|
if (width != m_DebuggerComponent.WindowRect.width)
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowRect = new Rect(_mDebuggerComponent.WindowRect.x, _mDebuggerComponent.WindowRect.y, width, _mDebuggerComponent.WindowRect.height);
|
m_DebuggerComponent.WindowRect = new Rect(m_DebuggerComponent.WindowRect.x, m_DebuggerComponent.WindowRect.y, width, m_DebuggerComponent.WindowRect.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
{
|
{
|
||||||
float height = _mDebuggerComponent.WindowRect.height;
|
float height = m_DebuggerComponent.WindowRect.height;
|
||||||
GUILayout.Label("Height:", GUILayout.Width(60f));
|
GUILayout.Label("Height:", GUILayout.Width(60f));
|
||||||
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
||||||
{
|
{
|
||||||
@ -133,16 +133,16 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
height = Mathf.Clamp(height, 100f, Screen.height - 20f);
|
height = Mathf.Clamp(height, 100f, Screen.height - 20f);
|
||||||
if (height != _mDebuggerComponent.WindowRect.height)
|
if (height != m_DebuggerComponent.WindowRect.height)
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowRect = new Rect(_mDebuggerComponent.WindowRect.x, _mDebuggerComponent.WindowRect.y, _mDebuggerComponent.WindowRect.width, height);
|
m_DebuggerComponent.WindowRect = new Rect(m_DebuggerComponent.WindowRect.x, m_DebuggerComponent.WindowRect.y, m_DebuggerComponent.WindowRect.width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
{
|
{
|
||||||
float scale = _mDebuggerComponent.WindowScale;
|
float scale = m_DebuggerComponent.WindowScale;
|
||||||
GUILayout.Label("Scale:", GUILayout.Width(60f));
|
GUILayout.Label("Scale:", GUILayout.Width(60f));
|
||||||
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
if (GUILayout.RepeatButton("-", GUILayout.Width(30f)))
|
||||||
{
|
{
|
||||||
@ -156,9 +156,9 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
scale = Mathf.Clamp(scale, 0.5f, 4f);
|
scale = Mathf.Clamp(scale, 0.5f, 4f);
|
||||||
if (scale != _mDebuggerComponent.WindowScale)
|
if (scale != m_DebuggerComponent.WindowScale)
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = scale;
|
m_DebuggerComponent.WindowScale = scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
@ -167,49 +167,49 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
{
|
{
|
||||||
if (GUILayout.Button("0.5x", GUILayout.Height(60f)))
|
if (GUILayout.Button("0.5x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 0.5f;
|
m_DebuggerComponent.WindowScale = 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("1.0x", GUILayout.Height(60f)))
|
if (GUILayout.Button("1.0x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 1f;
|
m_DebuggerComponent.WindowScale = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("1.5x", GUILayout.Height(60f)))
|
if (GUILayout.Button("1.5x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 1.5f;
|
m_DebuggerComponent.WindowScale = 1.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("2.0x", GUILayout.Height(60f)))
|
if (GUILayout.Button("2.0x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 2f;
|
m_DebuggerComponent.WindowScale = 2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("2.5x", GUILayout.Height(60f)))
|
if (GUILayout.Button("2.5x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 2.5f;
|
m_DebuggerComponent.WindowScale = 2.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("3.0x", GUILayout.Height(60f)))
|
if (GUILayout.Button("3.0x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 3f;
|
m_DebuggerComponent.WindowScale = 3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("3.5x", GUILayout.Height(60f)))
|
if (GUILayout.Button("3.5x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 3.5f;
|
m_DebuggerComponent.WindowScale = 3.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("4.0x", GUILayout.Height(60f)))
|
if (GUILayout.Button("4.0x", GUILayout.Height(60f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.WindowScale = 4f;
|
m_DebuggerComponent.WindowScale = 4f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
if (GUILayout.Button("Reset Layout", GUILayout.Height(30f)))
|
if (GUILayout.Button("Reset Layout", GUILayout.Height(30f)))
|
||||||
{
|
{
|
||||||
_mDebuggerComponent.ResetLayout();
|
m_DebuggerComponent.ResetLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
@ -43,7 +43,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
internal static readonly float DefaultWindowScale = 1f;
|
internal static readonly float DefaultWindowScale = 1f;
|
||||||
|
|
||||||
// private static readonly TextEditor s_TextEditor = new TextEditor();
|
// private static readonly TextEditor s_TextEditor = new TextEditor();
|
||||||
private IDebuggerService _mDebuggerService = null;
|
private IDebuggerModule _mDebuggerModule = null;
|
||||||
private Rect m_DragRect = new Rect(0f, 0f, float.MaxValue, 25f);
|
private Rect m_DragRect = new Rect(0f, 0f, float.MaxValue, 25f);
|
||||||
private Rect m_IconRect = DefaultIconRect;
|
private Rect m_IconRect = DefaultIconRect;
|
||||||
private Rect m_WindowRect = DefaultWindowRect;
|
private Rect m_WindowRect = DefaultWindowRect;
|
||||||
@ -99,10 +99,10 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ActiveWindow
|
public bool ActiveWindow
|
||||||
{
|
{
|
||||||
get { return _mDebuggerService.ActiveWindow; }
|
get { return _mDebuggerModule.ActiveWindow; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_mDebuggerService.ActiveWindow = value;
|
_mDebuggerModule.ActiveWindow = value;
|
||||||
enabled = value;
|
enabled = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,8 +149,8 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_instance = this;
|
_instance = this;
|
||||||
_mDebuggerService = AppServices.GetOrCreateScope<AppScope>().Register(new DebuggerService());
|
_mDebuggerModule = ModuleSystem.RegisterModule<IDebuggerModule, DebuggerModule>();
|
||||||
if (_mDebuggerService == null)
|
if (_mDebuggerModule == null)
|
||||||
{
|
{
|
||||||
Log.Error("Debugger manager is invalid.");
|
Log.Error("Debugger manager is invalid.");
|
||||||
return;
|
return;
|
||||||
@ -231,7 +231,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
private void OnGUI()
|
private void OnGUI()
|
||||||
{
|
{
|
||||||
if (_mDebuggerService == null || !_mDebuggerService.ActiveWindow)
|
if (_mDebuggerModule == null || !_mDebuggerModule.ActiveWindow)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// <param name="args">初始化调试器窗口参数。</param>
|
/// <param name="args">初始化调试器窗口参数。</param>
|
||||||
public void RegisterDebuggerWindow(string path, IDebuggerWindow debuggerWindow, params object[] args)
|
public void RegisterDebuggerWindow(string path, IDebuggerWindow debuggerWindow, params object[] args)
|
||||||
{
|
{
|
||||||
_mDebuggerService.RegisterDebuggerWindow(path, debuggerWindow, args);
|
_mDebuggerModule.RegisterDebuggerWindow(path, debuggerWindow, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -273,7 +273,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// <returns>是否解除注册调试器窗口成功。</returns>
|
/// <returns>是否解除注册调试器窗口成功。</returns>
|
||||||
public bool UnregisterDebuggerWindow(string path)
|
public bool UnregisterDebuggerWindow(string path)
|
||||||
{
|
{
|
||||||
return _mDebuggerService.UnregisterDebuggerWindow(path);
|
return _mDebuggerModule.UnregisterDebuggerWindow(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -283,7 +283,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// <returns>要获取的调试器窗口。</returns>
|
/// <returns>要获取的调试器窗口。</returns>
|
||||||
public IDebuggerWindow GetDebuggerWindow(string path)
|
public IDebuggerWindow GetDebuggerWindow(string path)
|
||||||
{
|
{
|
||||||
return _mDebuggerService.GetDebuggerWindow(path);
|
return _mDebuggerModule.GetDebuggerWindow(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -293,7 +293,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// <returns>是否成功选中调试器窗口。</returns>
|
/// <returns>是否成功选中调试器窗口。</returns>
|
||||||
public bool SelectDebuggerWindow(string path)
|
public bool SelectDebuggerWindow(string path)
|
||||||
{
|
{
|
||||||
return _mDebuggerService.SelectDebuggerWindow(path);
|
return _mDebuggerModule.SelectDebuggerWindow(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -328,7 +328,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
private void DrawWindow(int windowId)
|
private void DrawWindow(int windowId)
|
||||||
{
|
{
|
||||||
GUI.DragWindow(m_DragRect);
|
GUI.DragWindow(m_DragRect);
|
||||||
DrawDebuggerWindowGroup(_mDebuggerService.DebuggerWindowRoot);
|
DrawDebuggerWindowGroup(_mDebuggerModule.DebuggerWindowRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDebuggerWindowGroup(IDebuggerWindowGroup debuggerWindowGroup)
|
private void DrawDebuggerWindowGroup(IDebuggerWindowGroup debuggerWindowGroup)
|
||||||
@ -345,7 +345,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
names.Add(Utility.Text.Format("<b>{0}</b>", debuggerWindowNames[i]));
|
names.Add(Utility.Text.Format("<b>{0}</b>", debuggerWindowNames[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debuggerWindowGroup == _mDebuggerService.DebuggerWindowRoot)
|
if (debuggerWindowGroup == _mDebuggerModule.DebuggerWindowRoot)
|
||||||
{
|
{
|
||||||
names.Add("<b>Close</b>");
|
names.Add("<b>Close</b>");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
|
||||||
namespace AlicizaX.Debugger.Runtime
|
namespace AlicizaX.Debugger.Runtime
|
||||||
{
|
{
|
||||||
internal sealed partial class DebuggerService : IDebuggerService
|
internal sealed partial class DebuggerModule : IDebuggerModule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 调试器窗口组。
|
/// 调试器窗口组。
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.Debugger.Runtime
|
namespace AlicizaX.Debugger.Runtime
|
||||||
{
|
{
|
||||||
@ -7,7 +6,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// 调试器管理器。
|
/// 调试器管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UnityEngine.Scripting.Preserve]
|
[UnityEngine.Scripting.Preserve]
|
||||||
internal sealed partial class DebuggerService : ServiceBase, IDebuggerService
|
internal sealed partial class DebuggerModule : IDebuggerModule
|
||||||
{
|
{
|
||||||
private readonly DebuggerWindowGroup m_DebuggerWindowRoot;
|
private readonly DebuggerWindowGroup m_DebuggerWindowRoot;
|
||||||
private bool m_ActiveWindow;
|
private bool m_ActiveWindow;
|
||||||
@ -16,7 +15,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// 初始化调试器管理器的新实例。
|
/// 初始化调试器管理器的新实例。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UnityEngine.Scripting.Preserve]
|
[UnityEngine.Scripting.Preserve]
|
||||||
public DebuggerService()
|
public DebuggerModule()
|
||||||
{
|
{
|
||||||
m_DebuggerWindowRoot = new DebuggerWindowGroup();
|
m_DebuggerWindowRoot = new DebuggerWindowGroup();
|
||||||
m_ActiveWindow = false;
|
m_ActiveWindow = false;
|
||||||
@ -49,22 +48,20 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||||
void IServiceTickable.Tick(float deltaTime)
|
void IModuleUpdate.Update(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
if (!m_ActiveWindow)
|
if (!m_ActiveWindow)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_DebuggerWindowRoot.OnUpdate(Time.deltaTime,Time.realtimeSinceStartup);
|
m_DebuggerWindowRoot.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 关闭并清理调试器管理器。
|
/// 关闭并清理调试器管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnInitialize() { }
|
void IModule.Dispose()
|
||||||
|
|
||||||
protected override void OnDestroyService()
|
|
||||||
{
|
{
|
||||||
m_ActiveWindow = false;
|
m_ActiveWindow = false;
|
||||||
m_DebuggerWindowRoot.Shutdown();
|
m_DebuggerWindowRoot.Shutdown();
|
||||||
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
@ -6,7 +6,7 @@ using AlicizaX;
|
|||||||
namespace AlicizaX.Debugger.Runtime
|
namespace AlicizaX.Debugger.Runtime
|
||||||
{
|
{
|
||||||
/// <summary>调试器管理器接口。</summary>
|
/// <summary>调试器管理器接口。</summary>
|
||||||
public interface IDebuggerService:IService, IServiceTickable
|
public interface IDebuggerModule:IModule,IModuleUpdate
|
||||||
{
|
{
|
||||||
/// <summary>获取或设置调试器窗口是否激活。</summary>
|
/// <summary>获取或设置调试器窗口是否激活。</summary>
|
||||||
bool ActiveWindow { get; set; }
|
bool ActiveWindow { get; set; }
|
||||||
@ -1,164 +0,0 @@
|
|||||||
using AlicizaX;
|
|
||||||
using AlicizaX.Audio.Runtime;
|
|
||||||
using AlicizaX.Localization.Runtime;
|
|
||||||
using AlicizaX.ObjectPool;
|
|
||||||
using AlicizaX.Resource.Runtime;
|
|
||||||
using AlicizaX.Scene.Runtime;
|
|
||||||
using AlicizaX.UI.Runtime;
|
|
||||||
|
|
||||||
public static partial class GameApp
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取游戏基础组件。
|
|
||||||
/// </summary>
|
|
||||||
public static RootModule Base => RootModule.Instance;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取声音组件。
|
|
||||||
/// </summary>
|
|
||||||
public static IAudioService Audio
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_audio == null)
|
|
||||||
{
|
|
||||||
_audio = AppServices.App.Require<IAudioService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _audio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IAudioService _audio;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取本地化组件。
|
|
||||||
/// </summary>
|
|
||||||
public static ILocalizationService Localization
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_localization == null)
|
|
||||||
{
|
|
||||||
_localization = AppServices.App.Require<ILocalizationService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _localization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ILocalizationService _localization;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取对象池组件。
|
|
||||||
/// </summary>
|
|
||||||
public static IObjectPoolService ObjectPool
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_objectPool == null)
|
|
||||||
{
|
|
||||||
_objectPool = AppServices.App.Require<IObjectPoolService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _objectPool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IObjectPoolService _objectPool;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取有限状态机组件。
|
|
||||||
/// </summary>
|
|
||||||
public static IProcedureService Procedure
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_procedure == null)
|
|
||||||
{
|
|
||||||
_procedure = AppServices.App.Require<IProcedureService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _procedure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IProcedureService _procedure;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取Asset组件。
|
|
||||||
/// </summary>
|
|
||||||
public static IResourceService Resource
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_resource == null)
|
|
||||||
{
|
|
||||||
_resource = AppServices.App.Require<IResourceService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IResourceService _resource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取场景组件。
|
|
||||||
/// </summary>
|
|
||||||
public static ISceneService Scene
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_scene == null)
|
|
||||||
{
|
|
||||||
_scene = AppServices.App.Require<ISceneService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _scene;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ISceneService _scene;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取定时器组件。
|
|
||||||
/// </summary>
|
|
||||||
public static ITimerService Timer
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_timer == null)
|
|
||||||
{
|
|
||||||
_timer = AppServices.App.Require<ITimerService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _timer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ITimerService _timer;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取UI组件。
|
|
||||||
/// </summary>
|
|
||||||
public static IUIService UI
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_ui == null)
|
|
||||||
{
|
|
||||||
_ui = AppServices.App.Require<IUIService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _ui;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IUIService _ui;
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 6571694cccb20a64fb609396b9aada2b
|
guid: f06920c335296ea4694dab52e8bd493a
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
23
Runtime/GameApp/GameApp.Audio.cs
Normal file
23
Runtime/GameApp/GameApp.Audio.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX;
|
||||||
|
using AlicizaX.Audio.Runtime;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取声音组件。
|
||||||
|
/// </summary>
|
||||||
|
public static IAudioModule Audio
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_audio == null)
|
||||||
|
{
|
||||||
|
_audio = ModuleSystem.GetModule<IAudioModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _audio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IAudioModule _audio;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Audio.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Audio.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3e9812a7462843d48a0660209e7bf8aa
|
||||||
|
timeCreated: 1737440423
|
||||||
23
Runtime/GameApp/GameApp.Localization.cs
Normal file
23
Runtime/GameApp/GameApp.Localization.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX.Localization.Runtime;
|
||||||
|
using AlicizaX;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取本地化组件。
|
||||||
|
/// </summary>
|
||||||
|
public static ILocalizationModule Localization
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_localization == null)
|
||||||
|
{
|
||||||
|
_localization = ModuleSystem.GetModule<ILocalizationModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _localization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ILocalizationModule _localization;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Localization.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Localization.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a8c610d4005146509b2dd9a685bb15af
|
||||||
|
timeCreated: 1737440423
|
||||||
23
Runtime/GameApp/GameApp.ObjectPool.cs
Normal file
23
Runtime/GameApp/GameApp.ObjectPool.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX.ObjectPool;
|
||||||
|
using AlicizaX;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对象池组件。
|
||||||
|
/// </summary>
|
||||||
|
public static IObjectPoolModule ObjectPool
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_objectPool == null)
|
||||||
|
{
|
||||||
|
_objectPool = ModuleSystem.GetModule<IObjectPoolModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _objectPool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IObjectPoolModule _objectPool;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.ObjectPool.cs.meta
Normal file
3
Runtime/GameApp/GameApp.ObjectPool.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 10a6970b6ffc472d801816ffbdf8f1e1
|
||||||
|
timeCreated: 1737440423
|
||||||
22
Runtime/GameApp/GameApp.Procedure.cs
Normal file
22
Runtime/GameApp/GameApp.Procedure.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using AlicizaX;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取有限状态机组件。
|
||||||
|
/// </summary>
|
||||||
|
public static IProcedureModule Procedure
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_procedure == null)
|
||||||
|
{
|
||||||
|
_procedure = ModuleSystem.GetModule<IProcedureModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _procedure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IProcedureModule _procedure;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Procedure.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Procedure.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 577055df4d464c3999cdd432e009000e
|
||||||
|
timeCreated: 1763452169
|
||||||
23
Runtime/GameApp/GameApp.Resource.cs
Normal file
23
Runtime/GameApp/GameApp.Resource.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX.Resource.Runtime;
|
||||||
|
using AlicizaX;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Asset组件。
|
||||||
|
/// </summary>
|
||||||
|
public static IResourceModule Resource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_resource == null)
|
||||||
|
{
|
||||||
|
_resource = ModuleSystem.GetModule<IResourceModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IResourceModule _resource;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Resource.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Resource.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ba1f965b36d44ae2ac5820bc7fbb64ac
|
||||||
|
timeCreated: 1737440423
|
||||||
23
Runtime/GameApp/GameApp.Scene.cs
Normal file
23
Runtime/GameApp/GameApp.Scene.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX;
|
||||||
|
using AlicizaX.Scene.Runtime;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取场景组件。
|
||||||
|
/// </summary>
|
||||||
|
public static ISceneModule Scene
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_scene == null)
|
||||||
|
{
|
||||||
|
_scene = ModuleSystem.GetModule<ISceneModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _scene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ISceneModule _scene;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Scene.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Scene.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 214b27a946ff481fb669e0cf645398ee
|
||||||
|
timeCreated: 1737440423
|
||||||
23
Runtime/GameApp/GameApp.Timer.cs
Normal file
23
Runtime/GameApp/GameApp.Timer.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using AlicizaX;
|
||||||
|
using AlicizaX.Timer.Runtime;
|
||||||
|
|
||||||
|
public static partial class GameApp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取定时器组件。
|
||||||
|
/// </summary>
|
||||||
|
public static ITimerModule Timer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_timer == null)
|
||||||
|
{
|
||||||
|
_timer = ModuleSystem.GetModule<ITimerModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ITimerModule _timer;
|
||||||
|
}
|
||||||
3
Runtime/GameApp/GameApp.Timer.cs.meta
Normal file
3
Runtime/GameApp/GameApp.Timer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 669e997f79b44a11bf93c9da9af48397
|
||||||
|
timeCreated: 1737440423
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user