「游戏引擎Mojoc」(7)C使用goto label地址实现协程
2017-10-23 10:35
567 查看
C 语言实现协程,最困难的部分就是上下文信息的保存和还原。这样才能够做到,让协程在任意位置让出执行权限,稍后再恢复到中断位置继续执行。C 实现协程一般有几个方案。
使用第三方库来保存恢复上下文数据,比如ucontext。
使用汇编来保存上下文信息。
使用setjmp / longjmp 保存恢复上下文信息。
使用switch case的特性来做上下文断点继续,上下文信息需要用static变量保存。比如Protothreads。
使用线程来保存上下文信息。
Mojoc使用了类似switch case的解决方案,利用数据结构和static变量来保存上下文信息,使用宏来构建API调用。在实现的过程中,我发现C99中goto label地址的方法,可以替换掉switch case的结构,从而解决了switch case嵌套的问题。 另外,在API的设计上,借鉴了Unity的协程设计。
这个C的协程,完成了一下几个功能:
在协程执行的任意位置暂停,让出执行权限。
恢复协程继续上次中断的地方继续执行。
通过static变量和数据结构保存协程数据。
协程让出执行后,等待特定的帧数,时间,和其它协程完成。
&&label是语法,&&就是获得label的地址,并不是取地址再取地址。
goto *p 就是跳转到p指针,所指向的地址。
如果p指向了的是不正确的地址,程序会运行时崩溃。
label地址需要用void*指针存放。
协程让出执行权限后,可以等待帧,秒,其它协程三种类型。
等待执行,正在执行包括中断的也算在执行的,还有执行完成的。协程执行完成会进入缓存队列。
[step] 用来保存CoroutineRun执行到哪一行了。下次继续这一行执行。后面会介绍,使用宏定义LINE来捕获函数执行的函数,保存到step。
[Run] 就是一个C语言的函数,真正执行的协程函数。
[state] 用来标示协程处在什么状态。
[waitValue] 表示协程等待的数值,帧数还是时间。
[curWaitValue] 就是当前等待了多少数值,这个值抵达waitValue表示协程等待结束了。
[waitType] 表示等待的类型。是等待帧数,还是时间,还是其它协程完成。
[params] 是绑定的一个动态数组,存放需要在协程函数里使用的参数。
[waits] 也是一个动态数组,存放的是等待当前协程的其它协程。也就是说有多个协程在等待这个协程,当这个协程完成的时候会释放等待队列的其它协程。这里并没有使用一个指针保存等待的协程,而是选择了保存等待自己的协程数组。因为协程使用了缓存系统,一个协程结束,就要进入缓存队列,依赖它的协程需要立马得到通知。
但协程让出执行的时候,除了static和全局变量,其的它局部变量都会丢失,所以这里提供了一个数组来保存,需要记住的数据。
只有处在Begin和End宏之间,才能使用协程的中断函数。Begin的功能是在协程得到执行权限之后,直接调转到上次执行的地方继续执行。
API模拟了Unity中的协程设计。调用这几个宏的时候,会使用宏LINE构建一个goto的行号标签,并把这个标签保存在step变量之中。然后函数就return了,下次Begin的时候,会直接goto到step保存的地址。
goto的标签要放在return的后面,这样会先return,下次接着return后面继续执行。
「值得一试」
使用第三方库来保存恢复上下文数据,比如ucontext。
使用汇编来保存上下文信息。
使用setjmp / longjmp 保存恢复上下文信息。
使用switch case的特性来做上下文断点继续,上下文信息需要用static变量保存。比如Protothreads。
使用线程来保存上下文信息。
Mojoc使用了类似switch case的解决方案,利用数据结构和static变量来保存上下文信息,使用宏来构建API调用。在实现的过程中,我发现C99中goto label地址的方法,可以替换掉switch case的结构,从而解决了switch case嵌套的问题。 另外,在API的设计上,借鉴了Unity的协程设计。
这个C的协程,完成了一下几个功能:
在协程执行的任意位置暂停,让出执行权限。
恢复协程继续上次中断的地方继续执行。
通过static变量和数据结构保存协程数据。
协程让出执行后,等待特定的帧数,时间,和其它协程完成。
goto label地址
C99中,goto语句可以跳转到一个变量里,变量保存的是label的地址。int main() { static void* p = &&label; goto *p; printf("before label\n"); label: printf("after label\n"); return 0; }
&&label是语法,&&就是获得label的地址,并不是取地址再取地址。
goto *p 就是跳转到p指针,所指向的地址。
如果p指向了的是不正确的地址,程序会运行时崩溃。
label地址需要用void*指针存放。
思路
每一个抽象协程结构,除了执行函数,还会绑定状态,等待条件,参数等等。然后,会被注册到协程管理类。协程管理类,在update中每一帧去检测每个协程的状态,以决定执行的权限。而协程执行函数,相当于进行了分布计算。协程等待类型
typedef enum { /** * Coroutine wait for frame count to waitValue */ CoroutineWaitType_Frames, /** * Coroutine wait for second count to waitValue */ CoroutineWaitType_Seconds, /** * Coroutine wait for other Coroutine to finish */ CoroutineWaitType_Coroutines, /** * Coroutine just run forward */ CoroutineWaitType_Null, } CoroutineWaitType;
协程让出执行权限后,可以等待帧,秒,其它协程三种类型。
协程的状态
typedef enum { /** * Coroutine enter queue ready to running */ CoroutineState_Ready, /** * Coroutine has started to execute */ CoroutineState_Running, /** * Coroutine already finished and waiting for reuse */ CoroutineState_Finish, } CoroutineState;
等待执行,正在执行包括中断的也算在执行的,还有执行完成的。协程执行完成会进入缓存队列。
协程结构
typedef struct Coroutine Coroutine; typedef void (*CoroutineRun)(Coroutine* coroutine); struct Coroutine { /** * Record coroutine run step */ void* step; /** * Coroutine implement function */ CoroutineRun Run; /** * Coroutine current state */ CoroutineState state; /** * Coroutine wait value to execute */ float waitValue; /** * Record wait progress */ float curWaitValue; /** * Coroutine wait type */ CoroutineWaitType waitType; /** * Hold params for CoroutineRun to get * when coroutine finish clear but the param create memory control yourself */ ArrayList(void*) params[1]; /** * Hold Coroutines wait for this Coroutine to finish */ ArrayList(Coroutine*) waits [1]; };
[step] 用来保存CoroutineRun执行到哪一行了。下次继续这一行执行。后面会介绍,使用宏定义LINE来捕获函数执行的函数,保存到step。
[Run] 就是一个C语言的函数,真正执行的协程函数。
[state] 用来标示协程处在什么状态。
[waitValue] 表示协程等待的数值,帧数还是时间。
[curWaitValue] 就是当前等待了多少数值,这个值抵达waitValue表示协程等待结束了。
[waitType] 表示等待的类型。是等待帧数,还是时间,还是其它协程完成。
[params] 是绑定的一个动态数组,存放需要在协程函数里使用的参数。
[waits] 也是一个动态数组,存放的是等待当前协程的其它协程。也就是说有多个协程在等待这个协程,当这个协程完成的时候会释放等待队列的其它协程。这里并没有使用一个指针保存等待的协程,而是选择了保存等待自己的协程数组。因为协程使用了缓存系统,一个协程结束,就要进入缓存队列,依赖它的协程需要立马得到通知。
协程绑定数据
#define ACoroutine_AddParam(coroutine, value) \ AArrayList_Add(coroutine->params, value) /** * Get param value */ #define ACoroutine_GetParam(coroutine, index, type) \ AArrayList_Get(coroutine->params, index, type) /** * Get param valuePtr */ #define ACoroutine_GetPtrParam(coroutine, index, type) \ AArrayList_GetPtr(coroutine->params, index, type)
但协程让出执行的时候,除了static和全局变量,其的它局部变量都会丢失,所以这里提供了一个数组来保存,需要记住的数据。
协程标识
#define ACoroutine_Begin() \ if (coroutine->step != NULL) \ { \ goto *coroutine->step; \ } \ coroutine->state = CoroutineState_Running #define ACoroutine_End() \ coroutine->state = CoroutineState_Finish
只有处在Begin和End宏之间,才能使用协程的中断函数。Begin的功能是在协程得到执行权限之后,直接调转到上次执行的地方继续执行。
协程中断函数
/** * Construct goto label with line number */ #define ACoroutine_StepName(line) Step##line #define ACoroutine_Step(line) ACoroutine_StepName(line) /** * Called between ACoroutine_Begin and ACoroutine_End * * waitFrameCount: CoroutineRun wait frames and running again */ #define ACoroutine_YieldFrames(waitFrames) \ coroutine->waitValue = waitFrames; \ coroutine->curWaitValue = 0.0f; \ coroutine->waitType = CoroutineWaitType_Frames; \ coroutine->step = &&ACoroutine_Step(__LINE__); \ return; \ ACoroutine_Step(__LINE__): /** * Called between ACoroutine_Begin and ACoroutine_End * * waitSecond: CoroutineRun wait seconds and running again */ #define ACoroutine_YieldSeconds(waitSeconds) \ coroutine->waitValue = waitSeconds; \ coroutine->curWaitValue = 0.0f; \ coroutine->waitType = CoroutineWaitType_Seconds; \ coroutine->step = &&ACoroutine_Step(__LINE__); \ return; \ ACoroutine_Step(__LINE__): /** * Called between ACoroutine_Begin and ACoroutine_End * * waitCoroutine: CoroutineRun wait other Coroutine finished and running again */ #define ACoroutine_YieldCoroutine(waitCoroutine) \ coroutine->waitValue = 0.0f; \ coroutine->curWaitValue = 0.0f; \ coroutine->waitType = CoroutineWaitType_Coroutines; \ AArrayList_Add((waitCoroutine)->waits, coroutine); \ coroutine->step = &&ACoroutine_Step(__LINE__); \ return; \ ACoroutine_Step(__LINE__): /** * Called between ACoroutine_Begin and ACoroutine_End * sotp coroutine running */ #define ACoroutine_YieldBreak() \ coroutine->state = CoroutineState_Finish; \ return
API模拟了Unity中的协程设计。调用这几个宏的时候,会使用宏LINE构建一个goto的行号标签,并把这个标签保存在step变量之中。然后函数就return了,下次Begin的时候,会直接goto到step保存的地址。
goto的标签要放在return的后面,这样会先return,下次接着return后面继续执行。
如何使用
static void LoadingRun(Coroutine* coroutine) { static int progress = 0; //-------------------------------------------------------------------------------------------------- ACoroutineBegin(); for (; progress < progressSize; progress++) { ACoroutineYieldFrame(0); } ACoroutineYieldSecond(1.0f); ACoroutineEnd(); } static void OnReady() { ACoroutine->StartCoroutine(LoadingRun); }
最后
Mojoc的完整实现在这里Coroutine.h,Coroutine.c,游戏里使用协程来loading加载资源AppInit.c。「值得一试」
相关文章推荐
- 「游戏引擎Mojoc」(9)C使用二分查找实现泛型字典映射
- 「游戏引擎Mojoc」(6)NDK替换android_native_app_glue的实现
- 使用 RuPengGame游戏引擎包 建立游戏窗体 如鹏游戏引擎包下载地址 Thread Runnable 卖票实例
- 使用协程实现游戏状态机
- 「游戏引擎Mojoc」(8)C实现泛型ArrayList
- torque游戏引擎DTS文件的LOD实现(含reflection反射材质演示)-The LOD and reflection in DTS files
- Torque游戏性能优化篇--在TORQUE游戏引擎里使用含LOD的DTS模型让游戏获得更高的FPS
- 使用网络地址转换实现多服务器负载均衡
- 使用 Drools 规则引擎实现业务逻辑
- 和微软合作的garagegames公司推出基于XNA的游戏引擎TorqueX,支持快速开发(近期引擎免费提供使用)
- 游戏引擎剖析--第3部份: 内存使用,特效和API
- 游戏引擎及其间单实现
- 使用Antlr实现表达式引擎
- 使用php重新实现PHP脚本引擎内置函数
- 使用网络地址转换实现多服务器负载均衡
- 游戏引擎及其间单实现
- 用C++实现跨平台游戏开发之Allegro引擎
- 用C++实现跨平台游戏开发之Allegro引擎
- 使用标准GDI实现游戏品质的动画系统
- 使用php重新实现PHP脚本引擎内置函数