makefile
很早就想写了,高阶概念总是忘了又忘,一千个人一千个哈姆雷特,理解抽象概念的方式各有不同
对于一个源码文件而言,里面的内容只是一个个字符,机器是无法识别的,而词法分析器的作用类似于转义器,将一个个字符拆成若干个有特定意义的词,而这一过程称为词法分析,此时它也不能被机器(或者这个虚拟机)识别
这篇文章将会是一个系列,更新会比源码慢,文档写的也不会写的很完全,名字暂定
typelang
, C syntax-like
早在2019之前就想开发一门脚本语言,一是加深编译原理的理解,二是觉得程序员不应该消耗在语言特性上,也一直想为自己的服务端框架 skynet-x 写一门dsl,现在是用lua作为服务的脚本端。但由于的若约束性导致在开发的时候很多同时并不够优雅,总是以一种奇怪的方式来解决问题,Lua本身并没有任何问题,它被设计之初是为了修补C的不足,但它的语法设计却并不符合我的预期。
尽管它的性能是脚本语言中顶尖的,但是一些隐式写法并不能保证它的预期性能,如混合table
,过多的函数调用栈,字符串操作以及无类型系统。
关于类型系统有利有弊,但我个人的观点是宁愿多出30%的开发时间,从而减少70%的bug。
DatatableModule
是一个基于 kpb 编码的配置文件管理系统,它定义了一个配置文件的数据结构,并提供了相应的API来操作和访问配置文件。在Zen
中它是一个GameComponent
。它包含了一个代码生成器,和数据解析器和编码器。
数据表的加载只有4(2)个接口 LoadDatatable<T>(bool lazy)
GetRow<T>(int row)
以及一套同作用的异步接口,前者获取一整张表,后者获取某表的某一行数据,即对应的数据结构体。
Datatable
静默行为是 Lazy load
对于同一张数据表,它只会根据需要读取指定行然后才缓存,而不是一次性读取所有表格。
lazy load
,它不会加载表格的所有数据,而是按照需要动态一部分一部分的加载,直到全部加载完毕。
DatatableModule
加载接口提供同步和异步两种模式,也可以加载远程资源,依赖于 Resource
DatatableModule
提供代码和数据生成的编辑器,无需关注实现逻辑。
多种类型数据支持 bool,int,float,string,binary,int*,float*,string*
,满足绝大部分场景
(2024/05/23)增加了定长数据的支持,减少内存消耗。
基于kproto
编码协议,极小的二进制文件,以及极快的编解码速度。
栈内存映射,大部分情况下不需要开辟堆空间,节省一部分堆内存的分配,减少Mono Reserved
的分配。
EntityManager
是一个很重要的模块,像游戏开发本身就是视觉感知,而EntityManager
是对所有场景物体的统一抽象封装, 并提供一系列通用操作,EntityManager
本身是一个抽象类,提供了一定程度的通用性操作,但针对一些特殊情况我们还是需要针对特定问题特定实现(千万不想要想着做平,来自某家公司的教训:))
EntityManager
附带一个默认的 EntityController
,提供一些常规的实体控制,像 Zen
的UI框架就是基于 EntityManager
的一个具体实现。
抽象描述一个物体的实体,它的生命周期函数定义类似于GameComponent
,但是它的调度不由GameEntry
而是 GameComponent
。
简单来说,要创建一个物体首先我们需要定义它的逻辑模板(骨架)以及它的数据(描述)
举个例子
public struct EnemyData:IEntityData{
// 对于实体数据接口,资源名必不可少
public string Assets {get;set;}
public void OnCtor(){
// 有些时候,实体的数据更关卡等级或这地图这类外部数据挂钩,可能需要在这里动态设置一次
}
}
// 定义Entity的逻辑模板
public struct Enemy:Entity {
protected EnemyData userdata;
protected void OnCtor(){
// init setup...
}
protected void OnUpdate(float delta,float unscaleDelta){
// loop logic.
}
}
void dosomething(){
// 直接在默认的EntityManager中创建一个实体
GameEntry.GetComponent<EntityManager>().Load<Enemy>(new EnemyData());
}
对于实体的销毁,像敌人死亡,特效消失之类的,仅仅只需要设置一个 Alive
属性,即可完成,生命周期由EntityManger
自行决断。
需要注意的是 继承IEntityData
的数据模板是会一直复用的,它与 Entity
的复用规则不同,后者复用的是Entity
所持有的实体,因为它本身只是一系列函数集合且很少会带有数据并不会占用太多的内存,而前者大部分情况下是都是通过读表获取,复杂的实体数据可能会导致内存异常大,所以保留实体数据是EntityManager
的默认行为。
EntityManager
可能需要在某些大量对象时使用对象池(Entity
),或者内存池(EntityData
),但在大多数情况下,框架并不清楚是否需要对象池,或者是需要一个定长周期的物体(子弹或者特效)。
这个时候需要设置就非常有必要了,可以决断出是否需要且生命周期(keepalive)或者是需要多大的对象池,以及自动孵化的频率了。
Resource
是一个全自动化,且简单易用的资源管理系统,继承于GameComponet
实现 ,它内部使用RC
的方式来管理资源的引用,且不需要手动显示释放,而是通过拦截finalizer的方式,来达到释放的目的,比手动安全性更高,也更易用。
它仅提供了两组接口资源加载接口LoadAsset
LoadAssetAsync
,。并提供了虚拟化的方式加载,在编辑器模式下不会真正的构建AssetBundle
包,从而提供开发效率。
Resource
也包括流场景构建,可以将整个场景都作为热更新资源。
自动化检测文件变动
通过Radix
算法,监控资源变动,来自动构建manifest
。上层只需要关注资源本身,无需关注AssetBundle
包的构建。做到对上层完全无感。
自动化引用计数
通过 RC
+ finalizer,监控资源的引用,来自动释放资源。且无需轮询检查资源的引用计数,提供更好的性能,做到对上层完全无感。也没有手动释放接口,提高安全性和易用性
自动化冗余剔除 在构建的时候自动收集冗余资源,如像A->(C,D) B->(C) 此时(A,D)将会作为一个的bundle,而C则会单独作为一个bundle
Profile资源使用分析,用以在运行时统计各个资源的引用和加载数
自定义流处理,支持加密等自定义操作
多种模式支持
Builtin: 内建资源,默认将资源插入母包中。
Remote: 远程资源模式,仅需要配置远程资源地址即可,所有资源不会在本地存盘,只会在内存中使用,使用完后立即释放,防止被反编译资源
无感知资源更新
一般出现资源更新的时候,都是先下载到本地,然后再通过加载本地资源的方式实现, 现由FS
映射的远程目录,通过分流的方式,可以实现一边从远程直接读取,并copy另外一部分流到本地磁盘,减少重新读盘的情况。
对于下载过程对于玩家而言完全无感,其本身下载速度是可以被控制,当前并没有暴露此接口(因为带宽值是动态变化的,无法根据不同的条件找到一个合理值),只是需要设定一个百分率阈值,内部自动计算下载速度尽可能保证达到预期值。
淘汰策略 当某个Assetbundle的引用计数为0时候,底层不会立即释放它,而是根据它过往的加载次数判断下一次淘汰次数,越大将会越晚淘汰,当然也提供了强制卸载函数。
现阶段 已实现Resource
是依赖于Assetbundle
后续会抽象出一层 FS
以提供自定义流提供更多可操作性和安全性。
Resource
现在可以说是完全不能脱离Unity
独立运行,这不是一个好的方式,我期望提供更高的抽象满足多个引擎的需求。当完成这一步时只需要在Unity
中需要手动实现一个Adaptor
即可。