「游戏引擎Mojoc」(7)C使用goto label地址实现协程
来源:互联网 发布:网络新词语大全 编辑:程序博客网 时间:2024/06/09 15:19
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变量和数据结构保存协程数据。
- 协程让出执行后,等待特定的帧数,时间,和其它协程完成。
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」(7)C使用goto label地址实现协程
- 「游戏引擎Mojoc」(2)C代码风格
- 「游戏引擎Mojoc」(3)C面向对象编程
- 「游戏引擎Mojoc」(8)C实现泛型ArrayList
- C99 goto label地址实现C语言协程
- 「游戏引擎Mojoc」(1)简介
- 「游戏引擎Mojoc」(6)NDK替换android_native_app_glue的实现
- 「游戏引擎Mojoc」(5)快速指南
- 「游戏引擎Mojoc」(4)面向组件-状态机-消息驱动3合1编程模型
- C语言 goto 使用实例
- 使用游戏引擎实现的水波纹
- label j;goto j;
- 关于goto的label
- C/C++/Java goto语句的使用
- 游戏服务器引擎实现
- C语言(17)-----goto语句使用
- c语言goto语句的使用
- C语言中使用goto语句
- sub2ind与ind2sub
- Visual Studio 2015中的常用调试技巧分享
- c++重载操作符续
- spring mvc完成restful风格的url
- C. Maximum splitting--codeforces
- 「游戏引擎Mojoc」(7)C使用goto label地址实现协程
- java timer 指定某时间点执行
- 解决RedHat Enterprise Linux 6.4 需要注册才能使用yum源的问题
- JAVA面向对象练习05第二种
- 给定两个整数n和k,返回1 ... n中k个数的所有可能组合。
- 误删电脑硬盘上的文件,试试这个方法找回!
- KLT跟踪算法(Kanade-Lucas-Tomasi Tracking Method)
- conv2
- 二分查找 (循环、递归两种方法)