目录

  • 写在前面的一些废话
  • 一、框架基础抽象类
  • 二、框架入口
  • 三、引用池和非托管内存

写在前面的一些废话

最近项目核心模块要重构,负责非核心模块的我就清闲了不少,想给自己充充电,唔,好像也不算充电,反正就学习一点进阶的东西,研究一下框架的相关内容。
       跑到GitHub上找unity相关的框架,star最多的就是entitas。但是上周已经大概研究过unity的DOTS了,大致了解了ECS的思路,entitas等放假再看吧(先给自己立个flag)。然后就是EllanJiang大佬的GameFramework(在GameFramework的基础上,大佬还封装了个UnityGameFramework)。
       对着源码看了半天感觉有点懵,不懂模块为什么这样写。就去找文档和教程(EllanJiang大佬自己写的文档只有两三篇orz)。然后就发现了这些资料:
       笨木头GameFramework系列
       烟雨迷离半世殇GameFramework系列
       自然妙有猫仙人GameFramework系列
这三位的教程各有侧重,主要看的是猫仙人的,然鹅,猫大佬没写完就鸽了!!!所以后面部分就只好自己分析。写这个就算是学习笔记,记录一下(PS:GameFramework版本是2021年一月初的版本)。

首先,从整体上看GameFramework是Manager of Managers式的。1 所以先来看Managers(就算先看程序入口,还是得拐回来)。

一、框架基础抽象类

在Base文件夹下有个叫 GameFrameworkModule 的类。在工程里可以看到所有的Manager都是继承自这个类。

namespace GameFramework
{/// <summary>/// 游戏框架模块抽象类。/// </summary>internal abstract class GameFrameworkModule{/// <summary>/// 获取游戏框架模块优先级。/// </summary>/// <remarks>优先级较高的模块会优先轮询,并且关闭操作会后进行。</remarks>internal virtual int Priority{get{return 0;}}/// <summary>/// 游戏框架模块轮询。/// </summary>/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>internal abstract void Update(float elapseSeconds, float realElapseSeconds);/// <summary>/// 关闭并清理游戏框架模块。/// </summary>internal abstract void Shutdown();}
}

从逻辑上看,这个基类比较简单,只定义了模块的轮询方法,轮询的优先级以及关闭模块的方法。语法上简单说一下internal。internal关键字使得它所修饰的类、方法或者属性等只能在同一个程序集(dll或者exe)中被访问。2 插一句题外话,现在unity有了程序集定义文件 Assembly Definition File来代替dll,据说可以加快编译时间。
       每个模块的Manager除了继承这个基类以外,还会实现一个Manager接口。我的理解是:GameFrameworkModule这个类是各个Manager的更高层次的抽象,负责实现它们共有的轮询和关闭方法,用internal修饰;而接口则是声明了这个Manager具体的功能,同时外部利用这个接口来访问Manager。
       好了,框架最最基础的部分就是这些了。接下来去看程序入口。

二、框架入口

namespace GameFramework
{/// <summary>/// 游戏框架入口。/// </summary>public static class GameFrameworkEntry{private static readonly GameFrameworkLinkedList<GameFrameworkModule> s_GameFrameworkModules = new GameFrameworkLinkedList<GameFrameworkModule>();/// <summary>/// 所有游戏框架模块轮询。/// </summary>/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>public static void Update(float elapseSeconds, float realElapseSeconds){foreach (GameFrameworkModule module in s_GameFrameworkModules){module.Update(elapseSeconds, realElapseSeconds);}}/// <summary>/// 关闭并清理所有游戏框架模块。/// </summary>public static void Shutdown(){for (LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.Last; current != null; current = current.Previous){current.Value.Shutdown();}s_GameFrameworkModules.Clear();ReferencePool.ClearAll();Utility.Marshal.FreeCachedHGlobal();GameFrameworkLog.SetLogHelper(null);}/// <summary>/// 获取游戏框架模块。/// </summary>/// <typeparam name="T">要获取的游戏框架模块类型。</typeparam>/// <returns>要获取的游戏框架模块。</returns>/// <remarks>如果要获取的游戏框架模块不存在,则自动创建该游戏框架模块。</remarks>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));}if (!interfaceType.FullName.StartsWith("GameFramework.", StringComparison.Ordinal)){throw new GameFrameworkException(Utility.Text.Format("You must get a Game Framework module, but '{0}' is not.", interfaceType.FullName));}string moduleName = Utility.Text.Format("{0}.{1}", interfaceType.Namespace, interfaceType.Name.Substring(1));Type moduleType = Type.GetType(moduleName);if (moduleType == null){throw new GameFrameworkException(Utility.Text.Format("Can not find Game Framework module type '{0}'.", moduleName));}return GetModule(moduleType) as T;}/// <summary>/// 获取游戏框架模块。/// </summary>/// <param name="moduleType">要获取的游戏框架模块类型。</param>/// <returns>要获取的游戏框架模块。</returns>/// <remarks>如果要获取的游戏框架模块不存在,则自动创建该游戏框架模块。</remarks>private static GameFrameworkModule GetModule(Type moduleType){foreach (GameFrameworkModule module in s_GameFrameworkModules){if (module.GetType() == moduleType){return module;}}return CreateModule(moduleType);}/// <summary>/// 创建游戏框架模块。/// </summary>/// <param name="moduleType">要创建的游戏框架模块类型。</param>/// <returns>要创建的游戏框架模块。</returns>private static GameFrameworkModule CreateModule(Type moduleType){GameFrameworkModule module = (GameFrameworkModule)Activator.CreateInstance(moduleType);if (module == null){throw new GameFrameworkException(Utility.Text.Format("Can not create module '{0}'.", moduleType.FullName));}LinkedListNode<GameFrameworkModule> current = s_GameFrameworkModules.First;while (current != null){if (module.Priority > current.Value.Priority){break;}current = current.Next;}if (current != null){s_GameFrameworkModules.AddBefore(current, module);}else{s_GameFrameworkModules.AddLast(module);}return module;}}
}

这个类说是框架的入口,实质上就是the MANAGER of managers。先看属性,就声明了一个List:GameFrameworkLinkedList s_GameFrameworkModules(GameFrameworkLinkedList实际上就是一个内置了对象池的LinkedList),用来存储用到的所有Manager。方法里看到了两个老熟人:Update() 和 Shutdown() 。在这两个方法里遍历调用了各个Manager的Update() 和 Shutdown()。剩下的就是创建和获取Manager的方法,在创建时会根据Manager的Priority进行排序。

提一下CreateModule()里用到的**Activator.CreateInstance()**方法。这个方法可以通过传入一个类名,来获取该类的一个实例,通常会结合反射使用,也可以向上面一样在类工厂里动态创建本地类型,不用说这个方法会比较影响性能。

三、引用池和非托管内存

从GameFrameworkEntry的ShutDown()里可以看到,除了关闭各个模块以外还进行了引用池和非托管内存的释放:ReferencePool.ClearAll();和Utility.Marshal.FreeCachedHGlobal();

    public static partial class ReferencePool{private sealed class ReferenceCollection{private readonly Queue<IReference> m_References;private readonly Type m_ReferenceType;private int m_UsingReferenceCount;private int m_AcquireReferenceCount;private int m_ReleaseReferenceCount;private int m_AddReferenceCount;private int m_RemoveReferenceCount;public ReferenceCollection(Type referenceType){m_References = new Queue<IReference>(); m_ReferenceType = referenceType;m_UsingReferenceCount = 0;m_AcquireReferenceCount = 0;m_ReleaseReferenceCount = 0;m_AddReferenceCount = 0;m_RemoveReferenceCount = 0;}public Type ReferenceType{get{return m_ReferenceType;}}public int UnusedReferenceCount{get{return m_References.Count;}}public int UsingReferenceCount{get{return m_UsingReferenceCount;}}public int AcquireReferenceCount{get{return m_AcquireReferenceCount;}}public int ReleaseReferenceCount{get{return m_ReleaseReferenceCount;}}public int AddReferenceCount{get{return m_AddReferenceCount;}}public int RemoveReferenceCount{get{return m_RemoveReferenceCount;}}public T Acquire<T>() where T : class, IReference, new(){if (typeof(T) != m_ReferenceType){throw new GameFrameworkException("Type is invalid.");}m_UsingReferenceCount++;m_AcquireReferenceCount++;lock (m_References){if (m_References.Count > 0){return (T)m_References.Dequeue();}}m_AddReferenceCount++;return new T();}public IReference Acquire(){m_UsingReferenceCount++;m_AcquireReferenceCount++;lock (m_References){if (m_References.Count > 0){return m_References.Dequeue();}}m_AddReferenceCount++;return (IReference)Activator.CreateInstance(m_ReferenceType);}public void Release(IReference reference){reference.Clear();lock (m_References){if (m_EnableStrictCheck && m_References.Contains(reference)){throw new GameFrameworkException("The reference has been released.");}m_References.Enqueue(reference);}m_ReleaseReferenceCount++;m_UsingReferenceCount--;}public void Add<T>(int count) where T : class, IReference, new(){if (typeof(T) != m_ReferenceType){throw new GameFrameworkException("Type is invalid.");}lock (m_References){m_AddReferenceCount += count;while (count-- > 0){m_References.Enqueue(new T());}}}public void Add(int count){lock (m_References){m_AddReferenceCount += count;while (count-- > 0){m_References.Enqueue((IReference)Activator.CreateInstance(m_ReferenceType));}}}public void Remove(int count){lock (m_References){if (count > m_References.Count){count = m_References.Count;}m_RemoveReferenceCount += count;while (count-- > 0){m_References.Dequeue();}}}public void RemoveAll(){lock (m_References){m_RemoveReferenceCount += m_References.Count;m_References.Clear();}}}}

作者在引用池里定义了一个私有类:ReferenceCollection,这个类只是定义了很基础的添加、移除、获取和释放方法。另外,看这个代码总感觉指示释放和移除数量这俩变量没什么用,果然大佬思考问题的还是比较全面的,暂时不是很理解。(找到答案回来补上,这两个变量在editor模式下运行时,可以在inspector里看到)

public static partial class ReferencePool{private static readonly Dictionary<Type, ReferenceCollection> s_ReferenceCollections = new Dictionary<Type, ReferenceCollection>();private static bool m_EnableStrictCheck = false;/// <summary>/// 获取或设置是否开启强制检查。/// </summary>public static bool EnableStrictCheck{get{return m_EnableStrictCheck;}set{m_EnableStrictCheck = value;}}/// <summary>/// 获取引用池的数量。/// </summary>public static int Count{get{return s_ReferenceCollections.Count;}}/// <summary>/// 获取所有引用池的信息。/// </summary>/// <returns>所有引用池的信息。</returns>public static ReferencePoolInfo[] GetAllReferencePoolInfos(){int index = 0;ReferencePoolInfo[] results = null;lock (s_ReferenceCollections){results = new ReferencePoolInfo[s_ReferenceCollections.Count];foreach (KeyValuePair<Type, ReferenceCollection> referenceCollection in s_ReferenceCollections){results[index++] = new ReferencePoolInfo(referenceCollection.Key, referenceCollection.Value.UnusedReferenceCount, referenceCollection.Value.UsingReferenceCount, referenceCollection.Value.AcquireReferenceCount, referenceCollection.Value.ReleaseReferenceCount, referenceCollection.Value.AddReferenceCount, referenceCollection.Value.RemoveReferenceCount);}}return results;}/// <summary>/// 清除所有引用池。/// </summary>public static void ClearAll(){lock (s_ReferenceCollections){foreach (KeyValuePair<Type, ReferenceCollection> referenceCollection in s_ReferenceCollections){referenceCollection.Value.RemoveAll();}s_ReferenceCollections.Clear();}}/// <summary>/// 从引用池获取引用。/// </summary>/// <typeparam name="T">引用类型。</typeparam>/// <returns>引用。</returns>public static T Acquire<T>() where T : class, IReference, new(){return GetReferenceCollection(typeof(T)).Acquire<T>();}/// <summary>/// 从引用池获取引用。/// </summary>/// <param name="referenceType">引用类型。</param>/// <returns>引用。</returns>public static IReference Acquire(Type referenceType){InternalCheckReferenceType(referenceType);return GetReferenceCollection(referenceType).Acquire();}/// <summary>/// 将引用归还引用池。/// </summary>/// <param name="reference">引用。</param>public static void Release(IReference reference){if (reference == null){throw new GameFrameworkException("Reference is invalid.");}Type referenceType = reference.GetType();InternalCheckReferenceType(referenceType);GetReferenceCollection(referenceType).Release(reference);}/// <summary>/// 向引用池中追加指定数量的引用。/// </summary>/// <typeparam name="T">引用类型。</typeparam>/// <param name="count">追加数量。</param>public static void Add<T>(int count) where T : class, IReference, new(){GetReferenceCollection(typeof(T)).Add<T>(count);}/// <summary>/// 向引用池中追加指定数量的引用。/// </summary>/// <param name="referenceType">引用类型。</param>/// <param name="count">追加数量。</param>public static void Add(Type referenceType, int count){InternalCheckReferenceType(referenceType);GetReferenceCollection(referenceType).Add(count);}/// <summary>/// 从引用池中移除指定数量的引用。/// </summary>/// <typeparam name="T">引用类型。</typeparam>/// <param name="count">移除数量。</param>public static void Remove<T>(int count) where T : class, IReference{GetReferenceCollection(typeof(T)).Remove(count);}/// <summary>/// 从引用池中移除指定数量的引用。/// </summary>/// <param name="referenceType">引用类型。</param>/// <param name="count">移除数量。</param>public static void Remove(Type referenceType, int count){InternalCheckReferenceType(referenceType);GetReferenceCollection(referenceType).Remove(count);}/// <summary>/// 从引用池中移除所有的引用。/// </summary>/// <typeparam name="T">引用类型。</typeparam>public static void RemoveAll<T>() where T : class, IReference{GetReferenceCollection(typeof(T)).RemoveAll();}/// <summary>/// 从引用池中移除所有的引用。/// </summary>/// <param name="referenceType">引用类型。</param>public static void RemoveAll(Type referenceType){InternalCheckReferenceType(referenceType);GetReferenceCollection(referenceType).RemoveAll();}private static void InternalCheckReferenceType(Type referenceType){if (!m_EnableStrictCheck){return;}if (referenceType == null){throw new GameFrameworkException("Reference type is invalid.");}if (!referenceType.IsClass || referenceType.IsAbstract){throw new GameFrameworkException("Reference type is not a non-abstract class type.");}if (!typeof(IReference).IsAssignableFrom(referenceType)){throw new GameFrameworkException(Utility.Text.Format("Reference type '{0}' is invalid.", referenceType.FullName));}}private static ReferenceCollection GetReferenceCollection(Type referenceType){if (referenceType == null){throw new GameFrameworkException("ReferenceType is invalid.");}ReferenceCollection referenceCollection = null;lock (s_ReferenceCollections){if (!s_ReferenceCollections.TryGetValue(referenceType, out referenceCollection)){referenceCollection = new ReferenceCollection(referenceType);s_ReferenceCollections.Add(referenceType, referenceCollection);}}return referenceCollection;}}

这部分概括来说就是引用池的管理类。除了增加了一个强制检查的方法外,就是对ReferenceCollection做了简单封装。但是,作为一个基础类这个引用池用到的地方非常多。

下面这个类也是一个通用的基础类,从功能上来讲就是把对象和数据流进行相互转换,只不过重载比较多。

public static partial class Utility{/// <summary>/// Marshal 相关的实用函数。/// </summary>public static class Marshal{private const int BlockSize = 1024 * 4;private static IntPtr s_CachedHGlobalPtr = IntPtr.Zero;private static int s_CachedHGlobalSize = 0;/// <summary>/// 获取缓存的从进程的非托管内存中分配的内存的大小。/// </summary>public static int CachedHGlobalSize{get{return s_CachedHGlobalSize;}}/// <summary>/// 确保从进程的非托管内存中分配足够大小的内存并缓存。/// </summary>/// <param name="ensureSize">要确保从进程的非托管内存中分配内存的大小。</param>public static void EnsureCachedHGlobalSize(int ensureSize){if (ensureSize < 0){throw new GameFrameworkException("Ensure size is invalid.");}if (s_CachedHGlobalPtr == IntPtr.Zero || s_CachedHGlobalSize < ensureSize){FreeCachedHGlobal();int size = (ensureSize - 1 + BlockSize) / BlockSize * BlockSize;s_CachedHGlobalPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);s_CachedHGlobalSize = size;}}/// <summary>/// 释放缓存的从进程的非托管内存中分配的内存。/// </summary>public static void FreeCachedHGlobal(){if (s_CachedHGlobalPtr != IntPtr.Zero){System.Runtime.InteropServices.Marshal.FreeHGlobal(s_CachedHGlobalPtr);s_CachedHGlobalPtr = IntPtr.Zero;s_CachedHGlobalSize = 0;}}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <returns>存储转换结果的二进制流。</returns>public static byte[] StructureToBytes<T>(T structure){return StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)));}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <param name="structureSize">要转换的对象的大小。</param>/// <returns>存储转换结果的二进制流。</returns>internal static byte[] StructureToBytes<T>(T structure, int structureSize){if (structureSize < 0){throw new GameFrameworkException("Structure size is invalid.");}EnsureCachedHGlobalSize(structureSize);System.Runtime.InteropServices.Marshal.StructureToPtr(structure, s_CachedHGlobalPtr, true);byte[] result = new byte[structureSize];System.Runtime.InteropServices.Marshal.Copy(s_CachedHGlobalPtr, result, 0, structureSize);return result;}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <param name="result">存储转换结果的二进制流。</param>public static void StructureToBytes<T>(T structure, byte[] result){StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), result, 0);}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <param name="structureSize">要转换的对象的大小。</param>/// <param name="result">存储转换结果的二进制流。</param>internal static void StructureToBytes<T>(T structure, int structureSize, byte[] result){StructureToBytes(structure, structureSize, result, 0);}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <param name="result">存储转换结果的二进制流。</param>/// <param name="startIndex">写入存储转换结果的二进制流的起始位置。</param>public static void StructureToBytes<T>(T structure, byte[] result, int startIndex){StructureToBytes(structure, System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), result, startIndex);}/// <summary>/// 将数据从对象转换为二进制流。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structure">要转换的对象。</param>/// <param name="structureSize">要转换的对象的大小。</param>/// <param name="result">存储转换结果的二进制流。</param>/// <param name="startIndex">写入存储转换结果的二进制流的起始位置。</param>internal static void StructureToBytes<T>(T structure, int structureSize, byte[] result, int startIndex){if (structureSize < 0){throw new GameFrameworkException("Structure size is invalid.");}if (result == null){throw new GameFrameworkException("Result is invalid.");}if (startIndex < 0){throw new GameFrameworkException("Start index is invalid.");}if (startIndex + structureSize > result.Length){throw new GameFrameworkException("Result length is not enough.");}EnsureCachedHGlobalSize(structureSize);System.Runtime.InteropServices.Marshal.StructureToPtr(structure, s_CachedHGlobalPtr, true);System.Runtime.InteropServices.Marshal.Copy(s_CachedHGlobalPtr, result, startIndex, structureSize);}/// <summary>/// 将数据从二进制流转换为对象。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="buffer">要转换的二进制流。</param>/// <returns>存储转换结果的对象。</returns>public static T BytesToStructure<T>(byte[] buffer){return BytesToStructure<T>(System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), buffer, 0);}/// <summary>/// 将数据从二进制流转换为对象。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="buffer">要转换的二进制流。</param>/// <param name="startIndex">读取要转换的二进制流的起始位置。</param>/// <returns>存储转换结果的对象。</returns>public static T BytesToStructure<T>(byte[] buffer, int startIndex){return BytesToStructure<T>(System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)), buffer, startIndex);}/// <summary>/// 将数据从二进制流转换为对象。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structureSize">要转换的对象的大小。</param>/// <param name="buffer">要转换的二进制流。</param>/// <returns>存储转换结果的对象。</returns>internal static T BytesToStructure<T>(int structureSize, byte[] buffer){return BytesToStructure<T>(structureSize, buffer, 0);}/// <summary>/// 将数据从二进制流转换为对象。/// </summary>/// <typeparam name="T">要转换的对象的类型。</typeparam>/// <param name="structureSize">要转换的对象的大小。</param>/// <param name="buffer">要转换的二进制流。</param>/// <param name="startIndex">读取要转换的二进制流的起始位置。</param>/// <returns>存储转换结果的对象。</returns>internal static T BytesToStructure<T>(int structureSize, byte[] buffer, int startIndex){if (structureSize < 0){throw new GameFrameworkException("Structure size is invalid.");}if (buffer == null){throw new GameFrameworkException("Buffer is invalid.");}if (startIndex < 0){throw new GameFrameworkException("Start index is invalid.");}if (startIndex + structureSize > buffer.Length){throw new GameFrameworkException("Buffer length is not enough.");}EnsureCachedHGlobalSize(structureSize);System.Runtime.InteropServices.Marshal.Copy(buffer, startIndex, s_CachedHGlobalPtr, structureSize);return (T)System.Runtime.InteropServices.Marshal.PtrToStructure(s_CachedHGlobalPtr, typeof(T));}}}

对我来说,非托管的代码不怎么用到,甚至都不常见到。好在这个类里使用的api比较少,学习起来不费劲,趁这个机会记录下。首先是IntPtr。官方文档说是 A platform-specific type that is used to represent a pointer or a handle. 好吧,姑且可以认为是个指针。在Stack Overflow上有看到说 In general (across programming languages), a pointer is a number that represents a physical location in memory. A null pointer is (almost always) one that points to 0, and is widely recognized as “not pointing to anything”. Since systems have different amounts of supported memory, it doesn’t always take the same number of bytes to hold that number, so we call a “native size integer” one that can hold a pointer on any particular system. 那简单来说IntPtr就是一个会根据本机内存地址大小而改变的有范围整数。另外还看到这么个说法 A pointer is something that points to an address in memory. In managed languages you have references (the address can move around) while in unmanaged languages you have pointers (the address is fixed). Emmmmm,今天才知道这个区别。StructureToPtr() 的第三个参数为true时会释放掉要转换的对象指向的所有子结构。文档上是这么说的:
       另外,这里有个内存泄漏的案例。
       Copy() 方法原型重载太多了,直接在文档看吧
       最后还有个问题不明白,就是在EnsureCachedHGlobalSize() 里面定义要分配的空间大小 int size = (ensureSize - 1 + BlockSize) / BlockSize * BlockSize; 这里size为什么要这样计算?

概括一下,看了框架的最基础部分、入口以及涉及到的两个工具类,除了学习到一些语法、api以外,就只有一个想法,基类真的好抽象啊!


  1. 引用自猫仙人 ↩︎

  2. 直接看文档吧.(记一下,如果在命名空间直接声明一个类、结构体或接口或委托并且没有指明访问性的话,默认为internal。如果嵌套的话默认为private(接口除外)) ↩︎

GameFrameWork学习笔记(一)相关推荐

  1. ET学习笔记之五星麻将0

    五星麻将0客户端登录界面 前言 准备工作 配置报错 热更新模式 热更新程序集HotfixAssembly 热更新启动GotoHotfix 登录Login 登录通道LoginAisle 登录界面Logi ...

  2. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  3. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  4. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  5. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  6. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  7. 知识图谱学习笔记(1)

    知识图谱学习笔记第一部分,包含RDF介绍,以及Jena RDF API使用 知识图谱的基石:RDF RDF(Resource Description Framework),即资源描述框架,其本质是一个 ...

  8. 计算机基础知识第十讲,计算机文化基础(第十讲)学习笔记

    计算机文化基础(第十讲)学习笔记 采样和量化PictureElement Pixel(像素)(链接: 采样的实质就是要用多少点(这个点我们叫像素)来描述一张图像,比如,一幅420x570的图像,就表示 ...

  9. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

最新文章

  1. Mac 安装 Eclipse
  2. LINKs: Xamarin.Forms + Prism
  3. vue动态绑定class,tab切换非常好用
  4. Deep Learning源代码收集-持续更新…
  5. 使用DPM还原exchange 2013已删除邮箱数据
  6. jedis操作redis(二)
  7. 关于ie6下提交上传表单的注意事项
  8. 《SQL Server 2008从入门到精通》--20180716
  9. Django框架 之基础入门
  10. python 3.8.0安卓_Python 3.8.0 正式版发布,新特性初体验
  11. python实现同态滤波
  12. MVG推出微型紧凑天线测量系统CR-M8
  13. taptap(fake)
  14. 证明 总偏差平方和 = 回归平方和 + 残差平方和
  15. 提取网页内容-Python
  16. 计算机电子表格编辑栏,怎么在Excel中添加开发工具到工具栏
  17. 关于ie浏览器不能访问localhost的问题
  18. MATLAB中round函数的使用
  19. adb 模拟按键,调试按键,android测试可以用
  20. 如何组建权责明确、运营高效的数据团队

热门文章

  1. linux使用matlab
  2. 生物神经网络与人工神经网络
  3. 载波层叠调制在多电平变换器及两电平变换器中的应用
  4. ES6 JavaScript Promise的感性认知
  5. Python的列表操作
  6. Cartographer(三)思岚雷达rplidar ros驱动使用报错与解决
  7. 基于Rasa框架搭建中文机器人对话系统
  8. PyQt5 5.10.1 + SIP 4.19.8 + Eric6
  9. python保存Excel文件sheet_name是中文时出错:UnicodeDecodeError
  10. 单价数量和总价的公式_知道总价和数量怎么算单价