赞
踩
线程是指进程内的一个执行单元,也是进程内可调度的实体。区别如下:
Unity在每一帧(Frame)都会去处理对象上的协程,主要是在LateUpdate后去处理协程(检验协程的条件是否满足)
协程是对lua线程的封装,一个协程有自己的栈和调用链,所有协程之间的协作要考虑栈之间的数据交换,发生错误之后的栈恢复等等。
static int luaB_cocreate(lua_State *L){
lua_State *NL;
lua_checktype(L,1,LUA_TFUNCTION); //第一个参数一定是一个函数对象
NL = lua_newthread(L); //新建线程
lua_pushvalue(L,1); //将函数移到顶部
lua_xmove(L,NL,1); 将函数对象转移到NL
return 1;
///解释:
///L为原来的线程,NL是新建的线程,把L栈顶的函数转移给NL,然后把NL压入L的栈顶返回。现在Nl栈顶是一个函数对象,此即为协程的主函数。创建完毕则等着后面的启动(resume)
}
重点注意:
行为树是一个包含逻辑节点和行为节点的树结构,每次需要找出一个行为的时候,会从树的根节点出发,遍历各个节点,找出第一个和当前数据相符合的行为。
行为树由多种不同类型的节点构成,它们都拥有一个共同的核心功能,即它们会返回三种状态中的一个作为结果。这三种状态分别是:
比如一个“walk(行走)”的节点会在计算寻路和让角色保持行走的过程中持续返回“Running”来让角色保持这一状态。
如果寻路因为某些原因失败,或是除了某些状况让行走的行为不得不中止,那么这个节点会返回“Failure”来告诉它的父节点;
如果这个角色走到了指定的目的地,那么节点返回“Success”来表示这个行走的指令已经成功完成。
一共有三种节点类型,它们分别是:
分等级的行为树。有一个做决策的行为树A,和一个按照命令执行的行为树B。A根据游戏世界的情况做出决策,然后将命令放到Database里,然后B根据命令做出动作。由于两个行为树都放在一个Game Object里,所以Database是A、B共享的。通常,决策者A并不会每一帧都做出决策,而是设定一个冷却时间。
1.对于结构的各个成员,第一个成员位于偏移为0的位置,以后的每个数据成员的偏移量必须是 min(#pragma pack()指定的数,这个数据成员的自身长度)的倍数
2.在所有的数据成员完成各自对齐之后,结构或联合体本身也要进行对齐,对齐将按照 #pragram pack 指定的数值和结构或者联合体最大数据成员长度中比较小的那个 也就是 min(#pragram pack() , 长度最长的数据成员);
#pragram pack(n) 表示的是设置n字节对齐,vc6默认的是8
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。
typedef union TString{
L_Umaxalign dummy; //保证内存对齐提高cpu提取速度
struct{
CommonHeader; // 用于GC类型
lu_byte reserved; //是否是保留字
unsigned int hash; //该字符串的哈希值
size_t len; //字符串长度
} tsv;
} TString;
typedef union TKey{ struct{ TValuefields; int next; //用于标记链表中下一个节点 }nk; TValue tck; }Tkey; typedef struct Node{ TValue i_val; TKey i_key; }Node; typedef struct Table{ CommonHeader; lu_byte flags; //元方法的标记,用于查询table是否包含某个类别的元方法 lu_byte lsizenode; //表示table的hash部分大小 unsigned int sizearray; //table的数组部分大小 TValue *array; //table的array数组首节点 Node *node; //table的hash表首节点 Node *lastfree; //表示table的hash表空闲节点的游标 struct Table *metatable; //元表 GCObject *gclist; //table gc相关参数 } Table;
所以table会将部分整形key作为下标放在数组中,其余的整形key和其他类型的key都放在hash表中。
最简单的标记-清除算法,缺点是gc的时候不能被打断,所以会严重卡住主流程
luajit使用状态机来执行gc算法,共有6种状态:
1、luajit中有两个判断是否需要gc的宏:
1、内存分配算法优化
2、减少gc遍历对象,即减少那些明确常驻内存的gc对象遍历
c#调用lua的原理是lua的虚拟机
把所有的c#类的public成员变量、成员函数,导出到一个相对应的Wrap类中,而这些成员函数通过特殊的标记,映射到lua的虚拟机中,当在lua中调用相对应的函数时,直接调用映射进去的c# Wrap函数。
每一个refFunction就是将每个函数转为一个指针,然后添加到类的元表中,与将一个c函数注册到lua中是一样的。
每一个变量或属性石被包装成get_xxx,set_xxx函数注册添加到类的元表的gettag,settag表中去,用于调用和获取。
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] static int GetComponent(IntPtr L) { try { //获取栈中参数的个数 int count = LuaDLL.lua_gettop(L); //根据栈中元素的个数和元素的类型判断该使用那一个重载 if (count == 2 && TypeChecker.CheckTypes<string>(L, 2)) { //将栈底的元素取出来,这个obj在栈中是一个fulluserdata,需要先将这个fulluserdata转化成对应的c#实例,也就是调用这个GetComponent函数的GameObject实例 UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject)); //将栈底的上一个元素取出来,也就是GetComponent(string type)的参数 string arg0 = ToLua.ToString(L, 2); //通过obj,arg0直接第调用GetCompent(string type)函数 UnityEngine.Component o = obj.GetComponent(arg0); //将调用结果压栈 ToLua.Push(L, o); //返回参数的个数 return 1; } //另一个GetComponent的重载,跟上一个差不多,就不详细说明了 else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2)) { UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject)); System.Type arg0 = (System.Type)ToLua.ToObject(L, 2); UnityEngine.Component o = obj.GetComponent(arg0); ToLua.Push(L, o); return 1; } //参数数量或类型不对,没有找到对应的重载,抛出错误 else { return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent"); } } catch (Exception e) { return LuaDLL.toluaL_exception(L, e); } }
以table存取数据,应尽量将有序的数据与无序的数据分开存储在不同的table中。如果因为项目需求不得不维护一个有序和无序混合的table,而遍历处理时又要求按照顺序进行处理,那么直接通过ipairs和pairs没办法满足需求,这时候需要引入一个table来保存遍历顺序。
lua文件是以chunk(代码块)的方式存在的,其本质是一个函数
而加载代码文件,通常会见到require、dofile、loadfile等函数
在被声明的就那个代码块内有效
拷贝的数据是和其他数据共享一个table对象的,只有最顶层的变量数据是在新的table中。
function table.shallowCopy(tb)
local copy = {}
for k,v in pairs(tb) do
copy[k] = v
end
return copy
end
递归深拷贝,包含了元表的深拷贝
function table.deepCopy(tb) if tb == nil then reutrn nil --在这里,如果是return而不是return nil,那么在setmetatable函数中会报错 --第二个参数错误;因为在lua中nil也是一种变量类型,no return value和return nil是有区别的; end local copy = {} for k,v in pairs(tb) do if type(v) == 'table' then copy[k] = table.deepCopy(v) else copy[k] = v end end setmetatable(copy,table.deepCopy(getmetatable(tb))) return copy end
entry结构体
private struct Entry{
public int bashCode; //哈希值,如果没有被使用,则为-1
public int next; //下一个元素的下标索引,如果没有下一个则为-1
public TKey key; //存放元素的键
public TValue value; //存放元素的值
}
关键的私有变量
private int[] buckets; //hash桶
private Entru[] entries; //Entry 数组,存放元素
private int count ; //当前entries的index位置
private int vaersion; //当前版本,防止迭代过程中集合被更改
private int freeList; //被删除Entry在entries中的下标index,这个位置是空闲的
private int freeCount; //有多少个被删除的Entry,有多少个空闲的位置
private IEqualityComparer<TKey> comparer; //比较器
private KeyCollection keys; //存放key的集合
private ValueCollection values; //存放value的集合
初始化函数:
private void Initialize(int capacity){
int prime = HashHelpers.GetPrime(capacity);
this.buckets = new int[prime];
for (int i = 0; i < this.buckets.Length; i++)
{
this.buckets[i] = -1;
}
this.entries = new Entry<TKey, TValue>[prime];
this.freeList = -1;
}
参考网址:https://blog.csdn.net/zhaoguanghui2012/article/details/88105715
通过装箱和拆箱操作,能够在值类型和引用类型中架起一做桥梁.换言之,可以轻松的实现值类型与引用类型的互相转换,装箱和拆箱能够统一考察系统,任何类型的值最终都可以按照对象进行处理
在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。具体来说,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址,因此,编译器会给空类隐含加上一个字节,这样空类实例化之后就会拥有独一无二的内存地址。如果没有这一个字节的占位,那么空类就无所谓实例化了,因为实例化的过程就是在内存中分配一块地址。
静态方法是在类中使用staitc修饰的方法,在类定义的时候已经被装载和分配。而非静态方法是不加static关键字的方法,在类定义时没有占用内存,只有在类被实例化成对象时,对象调用该方法才被分配内存。
静态方法中只能调用静态成员或者方法,不能调用非静态方法或者非静态成员,而非静态方法既可以调用静态成员或者方法又可以调用其他的非静态成员或者方法。
类在什么时候被加载?
当我们启动一个app的时候,系统会创建一个进程,此进程会加载一个Dalvik VM的实例,然后代码就运行在DVM之上,类的加载和卸载,垃圾回收等事情都由DVM负责。也就是说在进程启动的时候,类被加载,静态变量被分配内存。
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接
连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差
具体步骤如下:
(1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;
(2)并为每个已发送的数据包启动一个超时定时器;
(3)如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;
(4)否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。
(5)接收方收到数据包后,根据数据包的序号排序,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。
Protobuf是一个网络通信协议,提供了高效率的序列化和反序列化机制,序列化就是把对象转换成二进制数据发送给服务端,反序列化就是将收到的二进制数据转换成对应的对象。
//rule type name tag
optional int32 a = 1; //这个定义不是赋值,它只是定义了a字段的tag,tag包含了数据类型(int32)和字段序号(1)
tag有三个作用,一个保证字段不重复,二是保证它是数据流中的位置,三是标记了数据类型。所以,tag是由fieldNumber和wireType组成,fieldNumber保证了字段不重复和它是数据流中的位置,wireType标记了数据类型
类别 | 排序方法 | 时间复杂度最好情况 | 时间复杂度最坏情况 | 时间复杂度平均情况 | 空间复杂度-辅助空间 | 稳定性 |
---|---|---|---|---|---|---|
插入排序 | 直接插入排序 | O(n) | O(n2) | O(n2) | O(1) | 稳定 |
插入排序 | 希尔排序 | O(n) | O(n2) | ~O(n1.3) | O(1) | 不稳定 |
交换排序 | 冒泡排序 | O(n) | O(n2) | ~O(n2) | O(1) | 稳定 |
交换排序 | 快速排序 | O(nlogn) | O(n2) | O(nlogn) | O(nlogn) | 不稳定 |
选择排序 | 直接选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
选择排序 | 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n2) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | |
基数排序 | O(n+m) | O(k*(n+m)) | O(k*(n+m)) | O(n+m) | 稳定 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。