多轮审查和修复
This commit is contained in:
@@ -10,15 +10,37 @@ namespace BaseGames.Core.Events
|
||||
{
|
||||
[Multiline] public string description;
|
||||
|
||||
public event Action<T> OnEventRaised;
|
||||
private event Action<T> _onEventRaisedBacking;
|
||||
#if UNITY_EDITOR
|
||||
private int _subscriberCount;
|
||||
#endif
|
||||
|
||||
public event Action<T> OnEventRaised
|
||||
{
|
||||
add
|
||||
{
|
||||
_onEventRaisedBacking += value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount++;
|
||||
#endif
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onEventRaisedBacking -= value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount--;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Raise(T value)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EventBusMonitor.Record(name, value?.ToString() ?? "null",
|
||||
OnEventRaised?.GetInvocationList().Length ?? 0);
|
||||
_subscriberCount,
|
||||
Time.frameCount);
|
||||
#endif
|
||||
OnEventRaised?.Invoke(value);
|
||||
_onEventRaisedBacking?.Invoke(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,15 +60,37 @@ namespace BaseGames.Core.Events
|
||||
{
|
||||
[Multiline] public string description;
|
||||
|
||||
public event Action OnEventRaised;
|
||||
private event Action _onEventRaisedBacking;
|
||||
#if UNITY_EDITOR
|
||||
private int _subscriberCount;
|
||||
#endif
|
||||
|
||||
public event Action OnEventRaised
|
||||
{
|
||||
add
|
||||
{
|
||||
_onEventRaisedBacking += value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount++;
|
||||
#endif
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onEventRaisedBacking -= value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount--;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Raise()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EventBusMonitor.Record(name, "<void>",
|
||||
OnEventRaised?.GetInvocationList().Length ?? 0);
|
||||
_subscriberCount,
|
||||
Time.frameCount);
|
||||
#endif
|
||||
OnEventRaised?.Invoke();
|
||||
_onEventRaisedBacking?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e424c1787e5be4fa918201b1830192
|
||||
guid: d5c798758acf2c64097cf4ff3b088530
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,9 +1,2 @@
|
||||
// 此文件已废弃。DamageInfo / DamageInfoEventChannelSO 已迁移至
|
||||
// Assets/Scripts/Combat/DamageInfo.cs (namespace BaseGames.Combat)
|
||||
// 程序集 BaseGames.Combat.asmdef
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
// 保留空命名空间,避免 .meta 文件冲突。
|
||||
// ReSharper disable once EmptyNamespace
|
||||
}
|
||||
// DamageInfo 及 DamageInfoEventChannelSO 定义位于 Assets/Scripts/Combat/DamageInfo.cs。
|
||||
namespace BaseGames.Core.Events { }
|
||||
|
||||
@@ -3,7 +3,7 @@ using UnityEngine;
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度变更事件频道。Phase 2 难度系统使用。
|
||||
/// 难度变更事件频道。
|
||||
/// 发布:DifficultyScalerSO / SettingsManager
|
||||
/// 订阅:所有需要感知当前难度的系统
|
||||
/// </summary>
|
||||
|
||||
@@ -11,29 +11,53 @@ namespace BaseGames.Core.Events
|
||||
public string ChannelName;
|
||||
public string Payload;
|
||||
public int ListenerCount;
|
||||
public int FrameCount;
|
||||
public System.DateTime Timestamp;
|
||||
}
|
||||
|
||||
private static readonly System.Collections.Generic.Queue<EventRecord> _records
|
||||
= new System.Collections.Generic.Queue<EventRecord>(256);
|
||||
private const int Capacity = 256;
|
||||
|
||||
public static System.Collections.Generic.IEnumerable<EventRecord> Records => _records;
|
||||
// 固定大小环形缓冲区,避免 Queue.Enqueue/Dequeue 产生的 GC 分配
|
||||
private static readonly EventRecord[] _buffer = new EventRecord[Capacity];
|
||||
private static int _head = 0; // 下一条写入的位置
|
||||
private static int _count = 0; // 当前有效记录数(≤ Capacity)
|
||||
|
||||
public static void Record(string channelName, string payload, int listenerCount)
|
||||
/// <summary>按时间顺序(最旧→最新)枚举所有已记录事件。</summary>
|
||||
public static System.Collections.Generic.IEnumerable<EventRecord> Records
|
||||
{
|
||||
if (_records.Count >= 256) _records.Dequeue();
|
||||
_records.Enqueue(new EventRecord
|
||||
get
|
||||
{
|
||||
int start = _count < Capacity ? 0 : _head;
|
||||
int total = System.Math.Min(_count, Capacity);
|
||||
for (int i = 0; i < total; i++)
|
||||
yield return _buffer[(start + i) % Capacity];
|
||||
}
|
||||
}
|
||||
|
||||
// frameCount 使用 int(与 Time.frameCount 类型一致)。
|
||||
// 在 @1000fps 持续运行约 24.8 天后发生有符号溢出(C# 默认 unchecked,环绕为负数)。
|
||||
// 对于 Editor 调试工具此溢出无实际影响,此处不做处理。
|
||||
public static void Record(string channelName, string payload, int listenerCount, int frameCount)
|
||||
{
|
||||
_buffer[_head] = new EventRecord
|
||||
{
|
||||
ChannelName = channelName,
|
||||
Payload = payload,
|
||||
ListenerCount = listenerCount,
|
||||
FrameCount = frameCount,
|
||||
Timestamp = System.DateTime.Now
|
||||
});
|
||||
};
|
||||
_head = (_head + 1) % Capacity;
|
||||
if (_count < Capacity) _count++;
|
||||
}
|
||||
|
||||
public static void Clear() => _records.Clear();
|
||||
public static void Clear()
|
||||
{
|
||||
_head = 0;
|
||||
_count = 0;
|
||||
}
|
||||
#else
|
||||
public static void Record(string channelName, string payload, int listenerCount) { }
|
||||
public static void Record(string channelName, string payload, int listenerCount, int frameCount) { }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,16 @@ namespace BaseGames.Core.Events
|
||||
/// </summary>
|
||||
public class EventChannelRegistry : MonoBehaviour, IEventChannelRegistry
|
||||
{
|
||||
public static EventChannelRegistry Instance { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, ScriptableObject> _channels = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
if (BaseGames.Core.ServiceLocator.GetOrDefault<IEventChannelRegistry>() != null)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
BaseGames.Core.ServiceLocator.Register<IEventChannelRegistry>(this);
|
||||
}
|
||||
|
||||
/// <summary>由 EventChannelRegistrar 在场景初始化时批量注册频道 SO。</summary>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661043851605d4849bef40ea15c556b4
|
||||
guid: 147bb5b987a0a244ba3a39c71852ca51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -1,9 +1,2 @@
|
||||
// 此文件已废弃。HitInfo / HitConfirmedEventChannelSO 已迁移至
|
||||
// Assets/Scripts/Combat/HitInfo.cs (namespace BaseGames.Combat)
|
||||
// 程序集 BaseGames.Combat.asmdef
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
// 保留空命名空间,避免 .meta 文件冲突。
|
||||
// ReSharper disable once EmptyNamespace
|
||||
}
|
||||
// HitInfo 及 HitConfirmedEventChannelSO 定义位于 Assets/Scripts/Combat/HitInfo.cs。
|
||||
namespace BaseGames.Core.Events { }
|
||||
|
||||
66
Assets/Scripts/Core/Events/ServiceLocator.cs
Normal file
66
Assets/Scripts/Core/Events/ServiceLocator.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 轻量服务定位器。通过类型键注册/查找服务,支持接口类型注册(依赖倒置)。
|
||||
/// <para><b>线程安全:</b>仅在 Unity 主线程调用。异步上下文(Task/Thread)不得访问此类。</para>
|
||||
/// </summary>
|
||||
public static class ServiceLocator
|
||||
{
|
||||
private static readonly Dictionary<Type, object> _services = new();
|
||||
|
||||
/// <summary>以接口类型 TInterface 注册实现 impl。</summary>
|
||||
public static void Register<TInterface>(TInterface impl)
|
||||
=> _services[typeof(TInterface)] = impl;
|
||||
|
||||
/// <summary>仅当尚未注册时才注册(防多场景重复注册同一服务)。</summary>
|
||||
public static void RegisterIfAbsent<TInterface>(TInterface impl)
|
||||
{
|
||||
if (!_services.ContainsKey(typeof(TInterface)))
|
||||
_services[typeof(TInterface)] = impl;
|
||||
}
|
||||
|
||||
/// <summary>查找服务。未注册时抛出 InvalidOperationException。</summary>
|
||||
public static TInterface Get<TInterface>()
|
||||
{
|
||||
if (_services.TryGetValue(typeof(TInterface), out var svc) && svc is TInterface typed)
|
||||
return typed;
|
||||
throw new InvalidOperationException(
|
||||
$"[ServiceLocator] Service '{typeof(TInterface).Name}' is not registered. "
|
||||
+ "Ensure GameServiceRegistrar.Awake() has run before this call.");
|
||||
}
|
||||
|
||||
/// <summary>安全版 Get:未注册时返回 fallback,不报错(适用于可选服务)。</summary>
|
||||
public static TInterface GetOrDefault<TInterface>(TInterface fallback = default)
|
||||
=> _services.TryGetValue(typeof(TInterface), out var svc) && svc is TInterface typed
|
||||
? typed : fallback;
|
||||
|
||||
/// <summary>
|
||||
/// 注销服务。场景卸载或 Manager OnDestroy 时调用,防止持有已销毁对象的引用。
|
||||
/// </summary>
|
||||
public static void Unregister<TInterface>()
|
||||
=> _services.Remove(typeof(TInterface));
|
||||
|
||||
/// <summary>
|
||||
/// 安全版注销:仅当注册的实例与 <paramref name="impl"/> 相同时才移除,
|
||||
/// 避免后注册的新实例被前一个实例的 OnDestroy 错误清除。
|
||||
/// </summary>
|
||||
public static void Unregister<TInterface>(TInterface impl)
|
||||
{
|
||||
if (_services.TryGetValue(typeof(TInterface), out var svc) && ReferenceEquals(svc, impl))
|
||||
_services.Remove(typeof(TInterface));
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>单元测试中替换服务实现。</summary>
|
||||
public static void OverrideForTest<TInterface>(TInterface mock)
|
||||
=> _services[typeof(TInterface)] = mock;
|
||||
|
||||
/// <summary>清空所有注册(测试用)。</summary>
|
||||
public static void Reset() => _services.Clear();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Core/Events/ServiceLocator.cs.meta
Normal file
11
Assets/Scripts/Core/Events/ServiceLocator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89a62da88ff5fbf46872aaaf10f111e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user