---root: node_r1 node_r2
--- ↗ ↖ ↗ ↖
---level1: node_l1_1 node_l1_2 node_l1_3
--- ↗ ↖
---level2: node_l2_1 node_l2_2
--- ↗ ↖
---level3: node_l3_1 node_l3_2
图例命名规则:node_l(第几层)_序号(1开始)
只有叶子节点可以被设置 具体数值,向上的树全部进行计算
图中 node_l3_1, node_l3_2, node_l2_2, node_l1_1, node_l1_3 可以被设置数值
某个节点被设置数值时序:(例如 node_l3_1 被设置了数值)
node_l3_1 通知回调
node_l2_1 计算 node_l3_1 和 node_l3_2 累加
node_l2_1 通知回调
node_l1_2 计算 node_l2_1 和 node_l2_2 累加
node_l1_2 通知回调
node_r1 计算 node_l1_1 和 node_l1_2 累加
node_r1 通知回调
node_r2 计算 node_l1_3 和 node_l1_2 累加
node_r2 通知回调
支持x型树。例如中心节点 node_l1_2。
因为最外层展示可能会有多个模块需求,比如道具修改对应红点是在活动,任务,背包 多个Root节点
不支持菱形结构。例如在图中 node_l3_2 同时指向 node_l2_2。
这个需求可能是合理的,但是实际应用场景非常少
菱形结构在递归中,可能会由于人为原因导致死循环,排查bug困难 或者 导致逻辑复杂度高
可以新增一个节点,同时增加计数来解决菱形结构的问题
构建关系树,应该是静态的,固定的。
这是设计层面的事情,不应该出现动态修改关系树的行为。
动态创建单独的无Link的节点,是被允许的。public接口(除RemoveListener)都是Create or Load
/*
root: node_r1 node_r2
↗ ↖ ↗ ↖
level1: node_l1_1 node_l1_2 node_l1_3
↗ ↖
level2: node_l2_1 node_l2_2
↗ ↖
level3: node_l3_1 node_l3_2
图例命名规则:node_l(第几层)_序号(1开始)
只有叶子节点可以被设置 具体数值,向上的树全部进行计算
图中 node_l3_1, node_l3_2, node_l2_2, node_l1_1, node_l1_3 可以被设置数值
某个节点被设置数值时序:(例如 node_l3_1 被设置了数值)
node_l3_1 通知回调
node_l2_1 计算 node_l3_1 和 node_l3_2 累加
node_l2_1 通知回调
node_l1_2 计算 node_l2_1 和 node_l2_2 累加
node_l1_2 通知回调
node_r1 计算 node_l1_1 和 node_l1_2 累加
node_r1 通知回调
node_r2 计算 node_l1_3 和 node_l1_2 累加
node_r2 通知回调
支持x型树。例如中心节点 node_l1_2。
因为最外层展示可能会有多个模块需求,比如道具修改对应红点 是在活动,任务,背包 多个Root节点
不支持菱形结构。例如在图中 node_l3_2 同时指向 node_l2_2。
这个需求可能是合理的,但是实际应用场景非常少
菱形结构在递归中,可能会由于人为原因导致死循环,排查bug困难 或者 导致逻辑复杂度高
可以新增一个节点,同时增加计数来解决菱形结构的问题
构建关系树,应该是静态的,固定的。
这是设计层面的事情,不应该出现动态修改关系树的行为。
动态创建单独的无Link的节点,是被允许的。public接口(除RemoveListener)都是Create or Load
监听变化: AddListener 和 RemoveListener 需要按照生命周期 成对出现,有Add必有Remove
*/
using System.Collections.Generic;
using System;
using UnityEngine;
public class RedDotManager
{
private static RedDotManager m_this = null;
public static RedDotManager Instance
{
get
{
if (m_this == null)
{
m_this = new RedDotManager();
}
return m_this;
}
}
///
/// 单个节点的集合
///
private class RedDotNode
{
///
/// 构造
///
/// 必须 唯一ID
public RedDotNode(string strKey)
{
m_strKey = strKey;
}
///
/// 唯一ID
///
public string m_strKey = null;
///
/// 计数变更的回调
///
public Action m_callBack = null;
///
/// 计数,每一层都需要记录,可能是相邻节点变化,本节点不需要重新计算自己的子节点
///
private int m_nRef = 0;
///
/// 父节点 可能会有多个
///
private List m_listParent = null; // 初始化为null,没必要初始化new浪费内存
///
/// 子节点 可能会有多个
///
private List m_listChild = null; // 初始化为null,没必要初始化new浪费内存
#region 父子节点相关
///
/// 父节点数量
///
private int ParentCount
{
get
{
if (m_listParent == null)
{
return 0;
}
return m_listParent.Count;
}
}
///
/// 子节点数量
///
public int ChildCount
{
get
{
if (m_listChild == null)
{
return 0;
}
return m_listChild.Count;
}
}
///
/// 检查目标节点是不是已经存在于树中
///
/// 目标节点
/// true=parent false=child
/// true=存在
public bool ContainsInTree(RedDotNode node, bool dir)
{
int nCount = dir ? ParentCount : ChildCount;
if (nCount > 0)
{
List list = dir ? m_listParent : m_listChild;
if (list.Contains(node))
{
return true;
}
else
{
for (int i = 0; i < nCount; i++)
{
if (list[i].ContainsInTree(node, dir))
{
return true;
}
}
}
}
return false;
}
///
/// 加入一个父节点
///
/// 单个节点
public void AddParent(RedDotNode node)
{
if (m_listParent == null)
{
m_listParent = new List();
}
m_listParent.Add(node);
}
///
/// 加入一个子节点
///
/// 单个节点
public void AddChild(RedDotNode node)
{
if (m_listChild == null)
{
m_listChild = new List();
}
m_listChild.Add(node);
}
#endregion
#region 计数相关
///
/// 计数
///
public int Ref
{
get
{
return m_nRef;
}
set
{
if (m_nRef != value)
{
m_nRef = value;
m_callBack?.Invoke(m_strKey, m_nRef);
RefreshParentRef();
}
}
}
///
/// 递归刷新所有父节点
///
private void RefreshParentRef()
{
int nCount = ParentCount;
for (int i = 0; i < nCount; i++)
{
m_listParent[i].CollectChildRef();
}
}
///
/// 累加子节点计数
///
private void CollectChildRef()
{
int nResult = 0;
int nCount = ChildCount;
for (int i = 0; i < nCount; i++)
{
var one = m_listChild[i];
nResult += one.m_nRef;
}
Ref = nResult; /// 这里触发递归
}
#endregion
}
///
/// 所有节点的集合, key=string唯一
///
private Dictionary m_dicNodes = new Dictionary();
///
/// 测试用,输出所有节点计数
///
public void LogRef()
{
#if UNITY_EDITOR
System.Text.StringBuilder strBuilder = new System.Text.StringBuilder();
foreach (var item in m_dicNodes)
{
strBuilder.Append(item.Value.m_strKey + " : " + item.Value.Ref + " ; ");
}
Debug.Log(strBuilder.ToString());
#endif
}
///
/// 创建或者获取一个节点
///
/// 唯一ID
/// 查询到 或者 新建的对象
private RedDotNode CreateOrGetNode(string strKey)
{
RedDotNode node;
if (!m_dicNodes.TryGetValue(strKey, out node))
{
node = new RedDotNode(strKey);
m_dicNodes.Add(strKey, node);
}
return node;
}
#region 事件注册
///
/// 添加加入一个注册
///
/// 唯一ID
/// 回调
public void AddListener(string strKey, Action callBack)
{
RedDotNode node = CreateOrGetNode(strKey);
// 这里使用了 delegate的特性+=, 允许添加相同callback,可能某些View层需要调用次数来处理逻辑。
node.m_callBack += callBack;
}
///
/// 移除一个注册
///
/// 唯一ID
/// 回调
public void RemoveListener(string strKey, Action callBack)
{
RedDotNode node;
if (!m_dicNodes.TryGetValue(strKey, out node))
{
Debug.LogWarning("RedDotManager RemoveListener, can not find key : " + strKey);
return;
}
node.m_callBack -= callBack;
}
#endregion
#region 父子节点关系处理
///
/// 建立父子关系
///
public void Link(string strKeyChild, string strKeyParent)
{
if (strKeyChild == strKeyParent)
{
Debug.LogError("RedDotManager Link, can not link self : " + strKeyChild);
return;
}
RedDotNode nodeChild = CreateOrGetNode(strKeyChild);
RedDotNode nodeParent = CreateOrGetNode(strKeyParent);
// 整个结构都是多叉树的链结构。所以检查单边,足够作为判断依据
if (nodeChild.ContainsInTree(nodeParent, true))
{
Debug.Log("RedDotManager Link, already exit in parent: " + strKeyChild + " -> " + strKeyParent);
return;
}
if (nodeChild.ContainsInTree(nodeParent, false))
{
Debug.Log("RedDotManager Link, already exit in child: " + strKeyChild + " -> " + strKeyParent);
return;
}
//之前没有连接关系,可以顺利连接
nodeChild.AddParent(nodeParent);
nodeParent.AddChild(nodeChild);
}
#endregion
#region 计数相关操作
///
/// 获取计数
///
/// 唯一ID
/// 计数
public int GetRef(string strKey)
{
var node = CreateOrGetNode(strKey); // 函数内部 处理返回,一定不会为空
return node.Ref;
}
///
/// 修改计数
///
/// 唯一ID
/// 是否是重置
/// 增量 or 数值
public void SetRef(string strKey, bool isReset, int nValue)
{
RedDotNode node = CreateOrGetNode(strKey);
if (!CheckIsLeafNode(node))
{
Debug.LogWarning("RedDotManager ChangeNodeRef, not Leaf node : " + strKey);
return;
}
if (isReset)
{
node.Ref = nValue;
}
else
{
node.Ref += nValue;
}
}
///
/// 检查自己是不是叶子节点
///
/// RedDotNode
/// 是否
private bool CheckIsLeafNode(RedDotNode node)
{
//单个 无父 无子 节点,允许主动修改
//无子节点 被认为是 叶子节点, 可以主动修改
return node.ChildCount == 0;
}
#endregion
}
测试代码:
void Start()
{
string[] arrRoot = new string[] { "r1", "r2" };
string[] arrLevel1 = new string[] { "11", "12", "13" };
string[] arrLevel2 = new string[] { "21", "22" };
string[] arrLevel3 = new string[] { "31", "32" };
var RDMgr = RedDotManager.Instance;
//Level1 -> Root
RDMgr.Link(arrLevel1[0], arrRoot[0]);
RDMgr.Link(arrLevel1[1], arrRoot[0]);
RDMgr.Link(arrLevel1[1], arrRoot[1]);
RDMgr.Link(arrLevel1[2], arrRoot[1]);
//Level2 -> Level1
RDMgr.Link(arrLevel2[0], arrLevel1[1]);
RDMgr.Link(arrLevel2[1], arrLevel1[1]);
//Level3 -> Level2
RDMgr.Link(arrLevel3[0], arrLevel2[0]);
RDMgr.Link(arrLevel3[1], arrLevel2[0]);
Debug.Log("---1---");
RDMgr.AddListener(arrRoot[0], OnRedDot);
RDMgr.SetRef(arrLevel3[0], false, 1);
RDMgr.LogRef();
RDMgr.SetRef(arrLevel3[0], true, 5);
RDMgr.LogRef();
Debug.Log("---2---");
RDMgr.SetRef(arrLevel3[1], false, 1);
RDMgr.LogRef();
RDMgr.SetRef(arrLevel3[0], false, -1);
RDMgr.LogRef();
Debug.Log("---3---");
RDMgr.SetRef(arrLevel2[1], true, 2);
RDMgr.LogRef();
Debug.Log("---4---");
RDMgr.RemoveListener(arrRoot[0], OnRedDot);
RDMgr.SetRef(arrLevel3[0], false, -1);
RDMgr.LogRef();
Debug.Log("---5---");
//测试异常情况, link父子节点时候,目标节点已经包含在树中
RDMgr.Link(arrLevel2[0], arrRoot[1]);
RDMgr.Link(arrLevel2[0], arrLevel2[0]);
}
private void OnRedDot(string strKey, int nRef)
{
Debug.Log("OnRedDot " + strKey + " " + nRef);
}
监听变化: AddListener 和 RemoveListener 需要按照生命周期 成对出现,有Add必有Remove
local pCallBack = Bind(self, self.FunctionCallBack) // 注:Lua里需要Bind
function xxx:FunctionCallBack()
...
end
self:AddListener(RedDotKey.Activity, pCallBack)
self:RemoveListener(RedDotKey.Activity, pCallBack) // 注:lua内部实现C# delegate使用的是table遍历对比
具体代码:(Lua + EmmyLua)
---@class RedDotKey 每100个跨度 为一个功能模块
local RedDotKey =
{
Activity = 100,
Activity_SevenDay = 101,
}
return ConstClass("RedDotKey", RedDotKey)
---@class RedDotNode
---@field eKey RedDotKey 唯一ID 键
---@field nRef number 计数,每一层都需要记录,可能是相邻节点变化,本节点不需要重新计算自己的子节点
---@field callBack function[] 计数变更的回调
---@field parent RedDotNode[] 父节点 可能会有多个
---@field child RedDotNode[] 子节点 可能会有多个
---@class RedDotManager : Singleton
---@field map table 键值对hash
local RedDotManager = BaseClass("RedDotManager", Singleton)
function RedDotManager:__init()
self.map = {}
self:Link(RedDotKey.Activity, RedDotKey.Activity_SevenDay)
end
---单个节点--------------------------------------------------------------------------------------------------------------
---添加加入一个注册
---@public
---@param eKey RedDotKey 唯一ID
---@param callBack function 回调
function RedDotManager:AddListener(eKey, callBack)
---@type RedDotNode
local node = self:CreateOrGetNode(eKey)
local result = table.indexof(node.callBack, callBack)
if result == false then
---没有找到 相同的注册, 正常加入 callback list
table.insert(node.callBack, callBack)
else
---找到了相同的callback
Debug.Log("!!! RedDotManager:AddNode, add same callback : "..tostring(eKey))
end
end
---移除一个注册
---@public
---@param eKey RedDotKey 唯一ID
---@param callBack function 回调
function RedDotManager:RemoveListener(eKey, callBack)
---@type RedDotNode
local node = self.map[eKey]
if node == nil then
return
end
local result = table.indexof(node.callBack, callBack)
if result ~= false then
table.remove(node.callBack, result)
else
---没有找到这个callback
Debug.Log("!!! RedDotManager:Remove, add same callback : "..tostring(eKey))
end
end
---创建或者获取一个节点
---@private
---@param eKey RedDotKey
function RedDotManager:CreateOrGetNode(eKey)
---@type RedDotNode
local node = self.map[eKey]
if node == nil then
self.map[eKey] = {}
node = self.map[eKey]
---初始化
node.eKey = eKey
node.nRef = 0
node.callBack = {}
node.parent = {}
node.child = {}
end
return node
end
---父子节点关系--------------------------------------------------------------------------------------------------------------
---建立父子关系
---@public
---@param eKeyChild RedDotKey 唯一ID
---@param eKeyParent RedDotKey 唯一ID
function RedDotManager:Link(eKeyChild, eKeyParent)
---@type RedDotNode
local nodeChild = self:CreateOrGetNode(eKeyChild)
---@type RedDotNode
local nodeParent = self:CreateOrGetNode(eKeyParent)
if self:CheckRepeat(nodeChild, nodeParent.child, true) then
Debug.Log("!!! RedDotManager:LinkParent already exit in c "..tostring(eKeyChild).." "..tostring(eKeyChild))
return
end
if self:CheckRepeat(nodeChild, nodeParent.parent, false) then
Debug.Log("!!! RedDotManager:LinkParent already exit in p "..tostring(eKeyChild).." "..tostring(eKeyChild))
return
end
---之前没有连接关系,可以顺利连接
table.insert(nodeChild.parent, nodeParent)
table.insert(nodeParent.child, nodeChild)
end
---检查自己是不是在树里,会造成无限循环的情况
---@private
---@param node RedDotNode 被检查的对象
---@param nodeLink RedDotNode 当前树的某一个节点
---@param isCorP boolean 递归向Child or parent, true=child
function RedDotManager:CheckRepeat(node, nodeLink, isCorP)
local nCount = #nodeLink
if nCount == 0 then
return false
end
if table.indexof(nodeLink, node) ~= false then
return true
end
for i = 1, nCount do
---@type RedDotNode
local one = nodeLink[i]
if self:CheckRepeat(node, isCorP and one.child or one.parent) then
return true
end
end
return false
end
---计数和通知--------------------------------------------------------------------------------------------------------------
---修改计数
---@public
---@param eKey RedDotKey 唯一ID
---@param isReset boolean 是否是重置
---@param nValue number 增量 or 数值
function RedDotManager:ChangeNodeRef(eKey, isReset, nValue)
---@type RedDotNode
local node = self:CreateOrGetNode(eKey)
if not self:CheckIsLeafNode(node) then
Debug.Log("!!! RedDotManager:ChangeNodeRef not Leaf node : "..tostring(eKey))
return
end
---缓存上一次的计数
local nTemp = node.nRef
if isReset then
node.nRef = nValue
else
node.nRef = node.nRef + nValue
end
---如果修改后计数不同,
if nTemp ~= node.nRef then
---调用回调 通知修改
self:DoCallBack(node)
---刷新上层
self:RefreshParentRef(node)
end
end
---检查自己是不是叶子节点
---@private
---@param node RedDotNode
---@return boolean 是否
function RedDotManager:CheckIsLeafNode(node)
---单个 无父 无子 节点,允许主动修改
---无子节点 被认为是 叶子节点, 可以主动修改
return #node.child == 0
end
---刷新父节点的计数
---@private
---@param node RedDotNode
function RedDotManager:RefreshParentRef(node)
local nCount = #node.parent
if nCount == 0 then
return
end
for i = 1,nCount do
---@type RedDotNode
local one = node.parent[i]
one.nRef = self:CollectChildRef(one)
self:DoCallBack(one)
---递归向上
self:RefreshParentRef(one)
end
end
---累加子节点计数
---@private
---@param node RedDotNode
---@return number 子节点的计数all
function RedDotManager:CollectChildRef(node)
local nResult = 0
local nCount = #node.child
for i = 1, nCount do
---@type RedDotNode
local one = node.child[i]
nResult = nResult + one.nRef
end
return nResult
end
---回调通知
---@private
---@param node RedDotNode
function RedDotManager:DoCallBack(node)
---调用回调 通知修改
local nCount = #node.callBack
for i = 1, nCount do
node.callBack[i](node.eKey, node.nRef)
end
end
程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄