时钟例程



基本的 PC 时钟只能是每秒触发 18.2 次, 这对于快节奏的动作游戏不是很适用. Allegro 可以用重新配置过的一个时钟例程来代替系统原来的,这个重编程过的时钟有 更高的触发频率,但是仍然以以前的速度调用 BIOS 处理程序. 你可以设置几个你自己 的虚拟时钟, 每个可以以不同的速度运行, Allegro 将不断的重编程时钟使它们在正 确的时间被调用.因为 Allegro 改变了 PIT 始终芯片的设置,它不能和 djgpp 的 libc 里的 uclock() 函数一起使用.



int install_timer();
加载 Allegro 时钟中断处理程序. 你必须在加载任何用户时钟例程之前,和在显示 鼠标,播放 FLI 动画或者 MIDI 音乐,使用其它的 GUI 例程之前执行它.



void remove_timer();
卸载 Allegro 时钟处理程序并且将时钟的控制权交还 BIOS. 你不必明显的调用它, 因为 allegro_exit() 将为你做这些.


int install_int(void (*proc)(), int speed);
加载用户始终处理程序, 参数 speed 的单位为万分之一秒.它和 install_int_ex(proc, MSEC_TO_TIMER(speed)) 作用相同.


int install_int_ex(void (*proc)(), int speed);
在用户时钟处理程序列表上加一个函数, 如果这个函数已经被加载,则调整它的速度. speed 的单位为硬件时钟单位, 即 1193181 分之一秒. 你可以利用以下的宏将其它 时间单位转换到这个单位上:


      SECS_TO_TIMER(secs)  - 给出两次触发间的秒数
      MSEC_TO_TIMER(msec)  - 给出两次触发间的万分之一秒数
      BPS_TO_TIMER(bps)    - 给出每秒触发多少次
      BPM_TO_TIMER(bpm)    - 给出每分钟触发多少次

如果没有空间再增加新的用户时钟程序, install_int_ex() 将返回一个负值, 否则返回零. 同时只能有 16 个时钟程序在使用,并且 Allegro 的其它部分 (GUI 代码, 鼠标显示例程, rest(), FLI 播放程序, 和 MIDI 播放程序) 需要加载它们自己的处理程序, 因此你要避免同时使用它过多.



你的函数将被 Allegro 中断处理程序调用而不是被处理器直接调用, 所以它能够 按正常的 C 函数来写而不需要特别的包装. 然而, 你必须知道,它将在一个中断中 被调用,而这给你希望在函数中做的事增加了许多约束.它不能使用大数量的堆栈, 不能调用任何 DOS 例程和使用调用了 DOS 例程的 C 库函数, 而且它必须被很快 的执行.不要在时钟处理程序里写太多的复杂代码: 一个基本的规则是你应该设置 一些标志然后在你的主控制循环里来回应它们.




在象 djgpp 这样的保护模式环境里, 内存被虚拟化并可以向磁盘交换. 由于 DOS 的不可重入, 如果磁盘交换发生在中断处理程序内部,系统将痛苦的死机, 所以你 需要确认你锁住了所有时钟例程中触及的所有内存 (包括代码和数据). Allegro 将锁住它用的所有东西,但是你有责任锁住你自己处理程序的. 宏 LOCK_VARIABLE (变量), END_OF_FUNCTION(函数名), 和 LOCK_FUNCTION(函数名) 可以被用来简化 这项任务.例如, 你想用一个中断处理程序来增加一个记数变量, 就应该这样写:





      volatile int counter;

void my_timer_handler() { counter++; }

END_OF_FUNCTION(my_timer_handler);

在你的初始化代码里,应该锁住内存:

      LOCK_VARIABLE(counter);
      LOCK_FUNCTION(my_timer_handler);

很明显,如果你使用了复杂的数据结构并且在这个处理程序中调用了其它的 函数,这就显得很笨拙,所以你应该使你的中断处理程序尽量简洁.


void remove_int(void (*proc)());
从用户中断例程列表中卸载一个函数.在程序的结束处, allegro_exit() 会自动运行它.

extern int i_love_bill;
如果被设为 TRUE, 打开专门的 'windows friendly' (对瘟都死友好) 时钟模式, 这将硬件时钟中断锁在 200 次每秒的速度,而不是对它动态重编程.这个模式减少 了时钟的精确性 (例如, rest() 将延时长度误差在 5 个百万分之一秒左右), 并且阻止了垂直回扫模拟器的工作 ( 在这个模式下对 timer_simulate_retrace() 的调用被忽略). 然而, 它带来的好处是, 使得 Allegro 程序可以工作在 windows 3.1 下, 而且使在 win95 的 DOS 模式下运行而没有出错信息. 这个标记应该在你加载始终模块前被设置, 而且在时钟在激活时不能改变它. 缺省状态下, allegro_init() 如果检测到 windows 的存在,将打开这个模式.





void timer_simulate_retrace(int enable);
时钟处理程序可以被用来模拟垂直回扫中断. 回扫中断在处理平滑动画时极其有用, 但不幸的是, VGA 硬件不支持它. EGA 可以,一些 SVGA 卡也可以,但这不够, 那些方法不够标准,来使其变的有用. Allegro 使这些运作起来依靠的是对时钟编程 使其成为一个普通的中断,当它认为下一次回扫将要发生时在中断处理程序里轮询 (polling) VGA 确认处于显示器刷新时的 sync(垂直回扫同步) 中. 这些在一些情 况下工作的非常好,但是它有一大堆限制:




- 在 SVGA 模式下不要在任何时候都用回扫模拟器. 它只能在某些芯片上用, 而在其它的芯片上不行, 并且它和所有的 VESA 实现程序有冲突. 回扫模拟器 仅仅在 VGA 13h 模式和 mode-X 下可信.


- 回扫模拟器不能工作在 win95 下, 因为 win95 在我试着从 PIT 里读取逝去的 时间时返回的是一些垃圾.如果有人知道我应该怎么做这些,请告诉我!


- 回扫模拟器在时钟处理程序里关掉了中断造成了许多的等待. 这将使你的整个系 统慢下来, 还可能在用 SB 1.0 声卡播放声音时造成无声 (因为它们不支持自动 初始化 DMA: SB 2.0 及以上版本没有问题).



由于要忍受这么多的问题, 我强烈建议你不要依赖于回扫模拟器. 如果你在 mode-X 下编程,而且不在乎你的程序能否在 win95 下使用, 它好极了 ,但交给用户一个可以 关掉它的开关却是个好主意.


回扫模拟器必须在你在 mode-X 下使用三缓冲函数时打开. 它也可以被用在简单的 回扫检测上, 因为轮询 vsync() 函数在声卡或时钟中断正好发生在回扫的同时时, 偶尔会丢失回扫. 当回扫中断模拟器打开, vsync() 将检查 retrace_count (回扫 计数器) 变量而不是轮询 VGA, 因此只有在它们被其它中断屏蔽时才会丢失回扫.





extern volatile int retrace_count;
如果回扫模拟器被加载,这个会随着每次垂直回扫而递增,否则它将每秒递增 70 次 (忽略回扫).这提供了一个避开加载用户时钟中断函数的麻烦而控制你的程序速度 的有效方法.

回扫变量的速度依赖于图形模式. 在 13h 模式 和 200/400 行的 mode-X 分辨率 下是每秒 70 次 回扫, 在 240/480 行模式下是 60 次. 它可以被慢到 50 次 (在 376x282 模式下) 也可能高到 92 次 (在 400x300 模式下).



extern void (*retrace_proc)();
如果回扫模拟器被加载, 这个函数将在每次垂直回扫时被调用, 否则将被每秒调用 70 次 (忽略回扫). 将其设置为 NULL 可以关掉回叫. 这个函数必须遵守和中断函 数相同的规则 (即, 它必须被锁住, 不能调用 DOS 或 libc 函数) 而且更甚的是: 它必须被 _非常_ 快的执行, 否则回对时钟同步造成混乱. 我能想到的唯一作用是 用来对调色板做一些巧妙的处理, 而三缓冲能够用 request_modex_scroll() 函数 来实现, 且 retrace_count 变量可以用来对你的代码记时. 如果你希望在 retrace_proc 里来改变调色板,则应该使用 inline _set_color() 函数而不是常 规的 set_color() 或 set_palette(), 而且你不要去尝试在一次回扫中改变二或 三个以上的调色板入口.





void rest(long time);
Allegro 一度接管了时钟, 基本的 delay() 函数将不能再工作,所以你必须使用这 个例程来代替. 时间的单位是百万分之一秒.


void rest_callback(long time, void (*callback)())
有点象 rest(), 不过它在等待所需的离开时间的同时将不停的调用指定的函数.





返回