diff --git a/CHANGELOG.md b/CHANGELOG.md index 911911a..57c13b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,17 @@ -#### **#2024/6/17 发布UniVue v1.0.0** +## **#2024/6/17 发布UniVue v1.0.0** 核心基础模块的完成 -#### **#2024/6/26修复UniVue v1.0.0中的BUG:** +## **#2024/6/26修复UniVue v1.0.0中的BUG:** 1. 基于ViewConfig构建的视图时,可能会重复生成UIEvent和UIBundle的问题; 2. LoogGrid组件的布局错误问题; -#### **#2024/6/26发布UniVue v1.0.1:核心模块优化** +## **#2024/6/26发布UniVue v1.0.1** 1. 优化VMTable,UI更新的复杂度为O(1)常数级; 2. VueConfig继承自ScriptableObject; @@ -20,17 +20,17 @@ -#### **#2024/6/27修复BUG** +## **#2024/6/27修复BUG** 1. 当ViewConfig的视图名称与文件名称不一致时导致错误的视图构建; -#### **#2024/6/27将版本v1.0.1合并为v1.0.0** +## **#2024/6/27将版本v1.0.1合并为v1.0.0** -#### **#2024/6/29发布版本v1.1.0** +## **#2024/6/29发布版本v1.1.0** 1. IBindableModel继承新的接口IConsumableModel,此接口能够实现不要将模型绑定到视图而是直接将数据更新到UI上,这样可以为以下两种场景带来方便: @@ -43,7 +43,7 @@ -#### **#2024/7/9发布版本v1.2.0** +## **#2024/7/9发布版本v1.2.0** 1. **支持自定义规则**:对之前的规则引擎部分的代码进行完全重构,现在支持自定义规则(但是现在无法取代默认的规则实现),只需要实现接口IRuleFilter即可; @@ -64,4 +64,15 @@ 7. 修复AtomModel、GroupModel对枚举类型无法进行执行UpdateModel()的bug; -8. 修复ModelUtil对枚举类型无法执行UpdateModel()的bug; \ No newline at end of file +8. 修复ModelUtil对枚举类型无法执行UpdateModel()的bug; + + + +## **#2024/7/12发布版本v1.3.0** + +1. 新增ViewLevel.Modal级别的视图等级,Modal模态视图打开时如果不关闭此视图无法打开任何视图(就是之前版本的forbid=true时行为); +2. **优化System级别的视图的逻辑:现在System级别的视图为同级互斥。同级的含义指:具有相同的父视图,所有的根视图均为同一级。同级下永远只有一个System级别的视图被打开;** +3. 当父视图没有被打开时不允许打开其子视图; +4. 为事件系统新增IEntityMapper接口,为AOT编译提供基于非反射创建对象的实现,如果你的项目要进行IL2CPP编译,如果事件回调的参数有自定义的类型,你应该手动注册相应实体类型的接口实现,以实现事件回调时能够正确获得参数值; +5. 优化视图的构建逻辑,**现在必须要求视图名称就是ViewObject.name,同时所有名称以"View"结尾的GameObject将被视为一个ViewObject对象**; +6. 优化API的使用; \ No newline at end of file diff --git a/README.md b/README.md index 83af534..874a3c5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,37 @@ -## 版本功能一栏表 +## **开发说明** + +**说明:**由于每个版本的源码有变动,实现方式也有变化,之前下面讲解中使用的图中的某些函数的名字已经改名,可能某些流程也已经改变,但是由于个人精力有限,实在挤不出时间写更完善的文档了,只能每次一点一点的写。emmmm,如果你愿意和我一起维护这个开源项目,不妨联系我和我一起撸起袖子加油干!**QQ:2947897147**; + +我会在博客中对UniVue中的功能的使用进行讲解已经底层的实现逻辑、算法的实现。由于目前的电脑配置很差,开启Unity后录屏很卡,所有无法进行开视频进行讲解,emmm,后面升级装备了会在B站开视频进行讲解。保证最多一天的时间你就精通UniVue。 + +CSDN个人博客:[Avalon712-CSDN博客](https://blog.csdn.net/m0_62135731?spm=1000.2115.3001.5343) + +这个仓库之后发布功能稳定的版本,因此很多最新的功能在这里是无法看见的,如果你想预览最新的功能,不妨fork一下这个仓库:https://github.com/Avalon712/UniVue-Develop 。这个仓库是我开发UniVue框架使用的项目,所有最新以及正在开发的功能都在这里可以看见,里面对每个功能都会有单元测试,也是学习UniVue框架的不二选择。 + + + +**UniVue的扩展框架:UniVue源生成器** + +仓库地址:https://github.com/Avalon712/UniVue-SourceGenerator + +关于UniVue中源生成器更详细的说明,请看这篇博客:[UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客](https://blog.csdn.net/m0_62135731/article/details/139525492?spm=1001.2014.3001.5501) + + + +**注:C#的源生器将会是完全替代反射的必要框架。通过生成低级代码完成对反射的替换,同时大幅度提高框架的易用性;** + + + +**版本说明** + +目前只对2021及其以上有支持,其它版本没试过,不知道什么情况,源码采用了很多C#9的语法,C#9似乎实在Unity2021开始支持的,如果你要使用源生器或许只能使用2021版本以上。同时不会对Unity都遗弃的功能进行支持,如:Text组件。因为如果对这些相同功能的组件也进行支持会导致ViewModel非常冗余,如果你要进行支持建议自己通过继承PropertyUI,然后看对于的组件是怎么实现更新逻辑的,照抄一遍就可以了。ViewModel的PropertyUI有将近30多个类,emmm,这就是我不打算支持的原因。后面PropertyUI还会进行扩展,可能会有70多个类。后续有时间了再看看要不要对2021版本以下的进行测试支持。 + + + +## 版本核心功能一栏表 ### **v1.0.0** @@ -21,16 +51,23 @@ -**版本说明** +### v1.1.0 -目前只对2021及其以上有支持,其它版本没试过,不知道什么情况,源码采用了很多C#9的语法,C#9似乎实在Unity2021开始支持的,如果你要使用源生器或许只能使用2021版本以上。同时不会对Unity都遗弃的功能进行支持,如:Text组件。因为如果对这些相同功能的组件也进行支持会导致ViewModel非常冗余,如果你要进行支持建议自己通过继承PropertyUI,然后看对于的组件是怎么实现更新逻辑的,照抄一遍就可以了。ViewModel的PropertyUI有将近40多个类,emmm,这就是我不打算支持的原因。后面PropertyUI还会进行扩展,可能会有70多个类。 +- **IConsumableModel接口**:在v1.0.0版本的基础上新增IConsumableModel接口的功能,实现更快的更新速度更少的查询缓存占用; +- **源生成器同步更新至v1.1.0**:为支持IConsumableModel接口的功能,UniVue的源生成器提供了自动生成更新逻辑的代码; -### v1.1.0 +### v1.2.0 -- **IConsumableModel接口**:在v1.0.0版本的基础上新增IConsumableModel接口的功能,实现更快的更新速度更少的查询缓存占用; -- **源生成器同步更新至v1.1.0**:为支持IConsumableModel接口的功能,UniVue的源生成器提供了自动生成更新逻辑的代码; +- **支持自定义规则**:v1.2.0版本中全面优化了规则引擎的代码,完全单独抽离出来成为一个独立的模块,UniVue的所有模块都依赖规则引擎模块; + + + +### v1.3.0 + +- **新增Modal模态视图**:处于ViewLevel.Modal级别的视图被打开后,在关闭它之前,不允许任何视图再打开; +- **优化System级别视图逻辑**:System级别的视图现在为同级互斥,具有相同父视图的视图为同一级,所有根视图(没有父视图的视图)为同一级; @@ -48,24 +85,6 @@ -## **开发说明** - -**说明:**由于源码有变动,之前下面讲解中使用的图中的某些函数的名字已经改名,可能某些流程也已经改变,但是由于个人精力有限,实在挤不出时间写更完善的文档了,只能每次一点一点的写。emmmm,如果你愿意和我一起维护这个开源项目,不妨联系我和我一起撸起袖子加油干!**QQ:2947897147**; - -我会在博客中对UniVue中的功能的使用进行讲解已经底层的实现逻辑、算法的实现。由于目前的电脑配置很差,开启Unity后录屏很卡,所有无法进行开视频进行讲解,emmm,后面升级装备了会在B站开视频进行讲解。保证最多一天的时间你就精通UniVue。 - -CSDN个人博客:[Avalon712-CSDN博客](https://blog.csdn.net/m0_62135731?spm=1000.2115.3001.5343) - -这个仓库之后发布功能稳定的版本,因此很多最新的功能在这里是无法看见的,如果你想预览最新的功能,不妨fork一下这个仓库:https://github.com/Avalon712/UniVue-Develop 。这个仓库是我开发UniVue框架使用的项目,所有最新以及正在开发的功能都在这里可以看见,里面对每个功能都会有单元测试,也是学习UniVue框架的不二选择。 - - - -**UniVue的扩展框架:UniVue源生成器** - -仓库地址:https://github.com/Avalon712/UniVue-SourceGenerator - -关于UniVue中源生成器更详细的说明,请看这篇博客:[UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客](https://blog.csdn.net/m0_62135731/article/details/139525492?spm=1001.2014.3001.5501) - ## 一、简介 @@ -79,34 +98,34 @@ CSDN个人博客:[Avalon712-CSDN博客](https://blog.csdn.net/m0_62135731?spm= ### 视图加载流程 - +见ViewBuilder类的实现 ### 数据绑定流程 - +见ViewUtil的实现 ### UI更新流程 - +见ViewUpdater或UIBundle的实现 ### 模型更新流程 - +见UIBundle的实现 ### 事件触发流程 - +见UIBundle、IBindableModel的实现 ### 资源卸载流程 - +见Vue ### 自动装配与卸载EventCall的执行逻辑 - +见EventManager的实现 ## 二、Model @@ -306,7 +325,7 @@ UniVue除了提供实现数据、视图的双向绑定外还提供了强大的 这个枚举类定义了常见的命名风格。注意:无论是哪种命名风格,指定UI组件名称都是必要的。 -#### 2)NamingRuleEngine +#### 2)RuleEngine 这个类实现了所有命名规则的解析、匹配。如果你的对命名还是不太清楚,可以看此类的源码,这个类是通过正则表达式来实现的解析和匹配。 @@ -747,6 +766,8 @@ UniVue除了提供实现数据、视图的双向绑定外还提供了强大的 注意,多个命名规则下的顺序:**数据绑定 & 路由事件 & 自定义事件** +**注:在v1.2.0以后无需关系顺序,任意顺序都可,同时无需再使用" & "隔开,但是基于可读性的基础上仍然建议这么做。** + **举例说明:** - Player_Level_Slider & Arg_Grade[level]_Slider @@ -825,7 +846,7 @@ View中具有嵌套关系时,在创建被嵌套的视图时,无需指定view 在Unity的最上方的工具栏可以看见"**UniVue**"字样,点击,会出现提供的三个扩展功能。 -### ConfigEditorWindow** +### ConfigEditorWindow 位置: **UniVu > ConfigEditor** @@ -843,9 +864,16 @@ View中具有嵌套关系时,在创建被嵌套的视图时,无需指定view - **以字符'~'开头的GameObject及其后代GameObject都不会被进行组件查找;** - **以字符'@'开头的GameObject不会被进行组件查找,但是其后代GameObject会被进行查找;** +- 你可以通过修改VueConfig文件修改上述字符,使用其它字符完成; ## 十、源生成器 -最近的大更新中使用了源生成器来提高效率,关于UniVue中的源生成器,请看这篇博客:[UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客](https://blog.csdn.net/m0_62135731/article/details/139525492?spm=1001.2014.3001.5501) \ No newline at end of file +最近的大更新中使用了源生成器来提高效率,关于UniVue中的源生成器,请看这篇博客:[UniVue更新日志:使用源生成器优化Model和ViewModel层的设计-CSDN博客](https://blog.csdn.net/m0_62135731/article/details/139525492?spm=1001.2014.3001.5501) + + + +## 十一、规则引擎 + +通过自定义实现IRuleFilter接口,使用规则引擎RuleEngine能够对任何ViewObject执行Filter操作,过滤完后你可以对过滤的结果进行自定义的处理。规则引擎是所有模块的基础,后续的所有核心功能都依赖规则引擎。 \ No newline at end of file diff --git a/UniVue/Runtime/Evt/EntityMapper.cs b/UniVue/Runtime/Evt/EntityMapper.cs index 6ac3483..84490d0 100644 --- a/UniVue/Runtime/Evt/EntityMapper.cs +++ b/UniVue/Runtime/Evt/EntityMapper.cs @@ -12,16 +12,11 @@ public sealed class EntityMapper private EntityMapper() { } /// - /// 将事件参数映射为一个对象类型 + /// 通过反射创建实体对象 /// - /// 对象类型 - /// 参数 - /// 映射对象 - public static object Map(Type type, EventArg[] args) + public static object CreateEntity(Type type) { - object instance = Activator.CreateInstance(type); - SetValues(instance, args); - return instance; + return Activator.CreateInstance(type); } /// diff --git a/UniVue/Runtime/Evt/EventArg.cs b/UniVue/Runtime/Evt/EventArg.cs index d54b0bc..bcb132a 100644 --- a/UniVue/Runtime/Evt/EventArg.cs +++ b/UniVue/Runtime/Evt/EventArg.cs @@ -33,7 +33,7 @@ internal EventArg(string name, UIType type, Component argUI) public void SetArgumentValue(object value) { - SupportableArgType argType = EventUtil.GetSupportableArgType(value.GetType()); + SupportableArgType argType = ReflectionUtil.GetSupportableArgType(value.GetType()); if (argType == SupportableArgType.None || ((int)argType) > 6) { diff --git a/UniVue/Runtime/Evt/EventCall.cs b/UniVue/Runtime/Evt/EventCall.cs index eac8e8a..8d76593 100644 --- a/UniVue/Runtime/Evt/EventCall.cs +++ b/UniVue/Runtime/Evt/EventCall.cs @@ -86,27 +86,31 @@ private void SetParameterValues() ParameterInfo parameter = parameters[i]; string argName = parameter.Name; - SupportableArgType argType = EventUtil.GetSupportableArgType(parameter.ParameterType); + SupportableArgType argType = ReflectionUtil.GetSupportableArgType(parameter.ParameterType); if (argType == SupportableArgType.None) { continue; } if ((int)argType < 7) { - SetArgMatchValue(argName, parameter.ParameterType, ref i, ref argType, ref args); + SetArgMatchValue(argName, parameter.ParameterType, i, argType, args); } else { switch (argType) { case SupportableArgType.Custom: - //重用之前创建的对象实例 - if (_parameters[i] != null) + IEntityMapper mapper = Vue.Event.GetEntityMapper(parameter.ParameterType); + if (mapper != null) { - EntityMapper.SetValues(_parameters[i], args); + if (_parameters[i] == null) + _parameters[i] = mapper.CreateEntity(); + mapper.SetValues(_parameters[i], args); } else { - _parameters[i] = EntityMapper.Map(parameter.ParameterType, args); + if (_parameters[i] == null) + _parameters[i] = EntityMapper.CreateEntity(parameter.ParameterType); + EntityMapper.SetValues(_parameters[i], args); } break; @@ -128,7 +132,7 @@ private void SetParameterValues() } } - private void SetArgMatchValue(string argName, Type parameterType, ref int i, ref SupportableArgType argType, ref EventArg[] args) + private void SetArgMatchValue(string argName, Type parameterType, int i, SupportableArgType argType, EventArg[] args) { //除开以上类型,以下这些类型将进行参数名与类型都匹配成功才能设置 for (int j = 0; j < args.Length; j++) @@ -204,17 +208,16 @@ private void SetArgMatchValue(string argName, Type parameterType, ref int i, ref return; } - if (valueType != parameterType) + if (valueType == parameterType) { -#if UNITY_EDITOR - LogUtil.Warning($"方法[{_call.Name}]: 参数名为{argName}的类型为{parameterType},与UI返回的事件参数类型{value.GetType()}不一致,无法正确进行赋值!"); -#endif + _parameters[i] = value; } +#if UNITY_EDITOR else { - _parameters[i] = value; + LogUtil.Warning($"方法[{_call.Name}]: 参数名为{argName}的类型为{parameterType},与UI返回的事件参数类型{value.GetType()}不一致,无法正确进行赋值!"); } - +#endif return; } } diff --git a/UniVue/Runtime/Evt/EventManager.cs b/UniVue/Runtime/Evt/EventManager.cs index 56d9cff..7497aa4 100644 --- a/UniVue/Runtime/Evt/EventManager.cs +++ b/UniVue/Runtime/Evt/EventManager.cs @@ -12,8 +12,13 @@ public sealed class EventManager private List _events; private List _calls; private List _autowires; + private List _mappers; - internal EventManager() { _events = new(18); _calls = new(18); } + internal EventManager() + { + _events = new List(20); + _calls = new List(10); + } internal List Events => _events; @@ -41,6 +46,27 @@ public void Signup(T register) where T : IEventRegister } } + /// + /// 注册实体映射,将EventArg[]映射为自定义对象 + /// + public void RegisterMapper(IEntityMapper mapper) + { + if (_mappers == null) + _mappers = new List(); + _mappers.Add(mapper); + } + + public IEntityMapper GetEntityMapper(Type entityType) + { + if (_mappers == null) return null; + for (int i = 0; i < _mappers.Count; i++) + { + if (_mappers[i].EntityType == entityType) + return _mappers[i]; + } + return null; + } + /// /// 自动装配EventCall /// diff --git a/UniVue/Runtime/Evt/IEntityMapper.cs b/UniVue/Runtime/Evt/IEntityMapper.cs new file mode 100644 index 0000000..409885a --- /dev/null +++ b/UniVue/Runtime/Evt/IEntityMapper.cs @@ -0,0 +1,13 @@ +using System; + +namespace UniVue.Evt +{ + public interface IEntityMapper + { + public Type EntityType { get; } + + public object CreateEntity(); + + public void SetValues(object entity, EventArg[] eventArgs); + } +} diff --git a/UniVue/Runtime/Evt/SupportableArgType.cs b/UniVue/Runtime/Evt/SupportableArgType.cs new file mode 100644 index 0000000..94b8407 --- /dev/null +++ b/UniVue/Runtime/Evt/SupportableArgType.cs @@ -0,0 +1,24 @@ + +namespace UniVue.Evt +{ + /// + /// EventCall支持的方法参数类型 + /// + public enum SupportableArgType + { + /// + /// 不被支持的类型 + /// + None, + Int, + Float, + String, + Enum, + Bool, + Sprite, + Custom, + UIEvent, + EventArg, + EventCall + } +} diff --git a/UniVue/Runtime/Rule/Filters/ModelFilter.cs b/UniVue/Runtime/Rule/Filters/ModelFilter.cs index 4adf83e..50113d3 100644 --- a/UniVue/Runtime/Rule/Filters/ModelFilter.cs +++ b/UniVue/Runtime/Rule/Filters/ModelFilter.cs @@ -20,7 +20,9 @@ namespace UniVue.Rule /// public sealed class ModelFilter : IRuleFilter { - private readonly int _typeFlag; + private int _typeFlag; + private bool _allowUIUpdateModel; + public string ModelName { get; private set; } public IBindableModel Model { get; private set; } @@ -29,15 +31,14 @@ public sealed class ModelFilter : IRuleFilter public UIBundle Bundle { get; private set; } - public ModelFilter(IBindableModel model, string modelName = null) + public ModelFilter(IBindableModel model, bool allowUIUpdateModel = true, string modelName = null) { _typeFlag = -1; Model = model; ModelType = model.GetType(); - ModelName = modelName; - Bundle = null; _typeFlag = GetTypeFlag(); ModelName = GetModelName(); + _allowUIUpdateModel = allowUIUpdateModel; } public bool Check(ref (Component, UIType) component, List results) @@ -54,7 +55,7 @@ public bool Check(ref (Component, UIType) component, List results) public void OnComplete(List results) { if (results.Count > 0) - Bundle = UIBundleBuilder.Build(Model, results); + Bundle = UIBundleBuilder.Build(Model, results, _allowUIUpdateModel); } diff --git a/UniVue/Runtime/Rule/GlobalRule.cs b/UniVue/Runtime/Rule/GlobalRule.cs index d14f40b..a9ae641 100644 --- a/UniVue/Runtime/Rule/GlobalRule.cs +++ b/UniVue/Runtime/Rule/GlobalRule.cs @@ -4,20 +4,17 @@ using UnityEngine; using UnityEngine.UI; using UniVue.Utils; -using UniVue.View.Views; namespace UniVue.Rule { - public static class GlobalRule + internal static class GlobalRule { /// /// 获取一个GameObject下所有的具有特殊命名的UI组件 /// - /// 要找到组件的根对象 - /// 指定当前的GameObject是否是ViewObject,如果是则需要传递此参数 - /// 要排除的后代(这些GameObject的后代都不会进行查找) + /// 要过滤的根对象 /// IEnumerable> - public static IEnumerable> Filter(GameObject gameObject, IView view = null, params GameObject[] exclude) + public static IEnumerable> Filter(GameObject gameObject) { char SKIP_ALL_DESCENDANT_SYMBOL = Vue.Config.SkipDescendantNodeSeparator; char SKIP_CURRENT_SYMBOL = Vue.Config.SkipCurrentNodeSeparator; @@ -27,30 +24,6 @@ public static IEnumerable> Filter(GameObject gameO ValueTuple result = new(null, UIType.None); queue.Enqueue(root); - //获取所有要跳过查找的GameObject节点 - List skipObjs = null; - if (view != null && !string.IsNullOrEmpty(view.Root)) - { - skipObjs = new List(); - using (var v = view.GetNestedViews().GetEnumerator()) - { - while (v.MoveNext() && v.Current != null) - { - skipObjs.Add(v.Current.ViewObject.transform); - } - } - } - - if (exclude != null) - { - if (skipObjs == null) - skipObjs = new List(exclude.Length); - for (int i = 0; i < exclude.Length; i++) - { - skipObjs.Add(exclude[i].transform); - } - } - while (queue.Count > 0) { Transform parent = queue.Dequeue(); @@ -59,9 +32,7 @@ public static IEnumerable> Filter(GameObject gameO { Transform child = parent.GetChild(i); - if (child.name.StartsWith(SKIP_ALL_DESCENDANT_SYMBOL)) { continue; } - - if (skipObjs != null && skipObjs.Contains(child)) { continue; } + if (child.name.StartsWith(SKIP_ALL_DESCENDANT_SYMBOL) || child.name.EndsWith("View")) { continue; } //非叶子节点再入队 if (child.childCount != 0) @@ -91,52 +62,32 @@ private static void SetResult(ref ValueTuple result, GameObje { case UIType.Image: { - Image image = gameObject.GetComponent(); - if (image != null) - { - result.Item1 = image; - result.Item2 = UIType.Image; - } + result.Item1 = gameObject.GetComponent(); + result.Item2 = UIType.Image; break; } case UIType.TMP_Dropdown: { - TMP_Dropdown dropdown = gameObject.GetComponent(); - if (dropdown != null) - { - result.Item1 = dropdown; - result.Item2 = UIType.TMP_Dropdown; - } + result.Item1 = gameObject.GetComponent(); + result.Item2 = UIType.TMP_Dropdown; break; } case UIType.TMP_Text: { - TMP_Text text = gameObject.GetComponent(); - if (text != null) - { - result.Item1 = text; - result.Item2 = UIType.TMP_Text; - } + result.Item1 = gameObject.GetComponent(); + result.Item2 = UIType.TMP_Text; break; } case UIType.TMP_InputField: { - TMP_InputField input = gameObject.GetComponent(); - if (input != null) - { - result.Item1 = input; - result.Item2 = UIType.TMP_InputField; - } + result.Item1 = gameObject.GetComponent(); + result.Item2 = UIType.TMP_InputField; break; } case UIType.Button: { - Button button = gameObject.GetComponent