- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档 - 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理 - 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言 - 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则 - 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护 - 新增 Network 语义扫描、模板保护和 App 组件生命周期测试 - 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试 - 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
76 lines
2.7 KiB
C#
76 lines
2.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
namespace ShrinkEventBus
|
|
{
|
|
internal static class EventCloneUtility
|
|
{
|
|
private static readonly object CacheLock = new();
|
|
private static readonly Dictionary<Type, FieldInfo[]> FieldCache = new();
|
|
|
|
public static TEvent CloneForDetachedDispatch<TEvent>(TEvent source) where TEvent : EventBase
|
|
{
|
|
if (source == null)
|
|
throw new ArgumentNullException(nameof(source));
|
|
|
|
if (Activator.CreateInstance(source.GetType()) is not TEvent clone)
|
|
throw new InvalidOperationException(
|
|
$"Cannot clone event type {source.GetType().FullName}. A public parameterless constructor is required.");
|
|
|
|
// 先固化 EventId,让克隆与原事件共享同一个派发标识
|
|
_ = source.EventId;
|
|
CopyFields(source, clone);
|
|
clone.ReleaseAction = null;
|
|
clone.IsInPool = false;
|
|
return clone;
|
|
}
|
|
|
|
private static void CopyFields(EventBase source, EventBase target)
|
|
{
|
|
var fields = GetCopyableFields(source.GetType());
|
|
for (var i = 0; i < fields.Length; i++)
|
|
fields[i].SetValue(target, fields[i].GetValue(source));
|
|
}
|
|
|
|
private static FieldInfo[] GetCopyableFields(Type type)
|
|
{
|
|
lock (CacheLock)
|
|
{
|
|
if (FieldCache.TryGetValue(type, out var cached))
|
|
return cached;
|
|
|
|
var fields = new List<FieldInfo>();
|
|
var currentType = type;
|
|
while (currentType != null && currentType != typeof(object))
|
|
{
|
|
var declaredFields = currentType.GetFields(BindingFlags.Instance | BindingFlags.Public |
|
|
BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
|
for (var i = 0; i < declaredFields.Length; i++)
|
|
{
|
|
var field = declaredFields[i];
|
|
if (field.IsStatic)
|
|
continue;
|
|
if (ShouldSkipField(field))
|
|
continue;
|
|
|
|
fields.Add(field);
|
|
}
|
|
|
|
currentType = currentType.BaseType;
|
|
}
|
|
|
|
cached = fields.ToArray();
|
|
FieldCache[type] = cached;
|
|
return cached;
|
|
}
|
|
}
|
|
|
|
private static bool ShouldSkipField(FieldInfo field)
|
|
{
|
|
return field.Name is "<ReleaseAction>k__BackingField"
|
|
or "<IsInPool>k__BackingField";
|
|
}
|
|
}
|
|
}
|