1
0

y_timers.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. /*----------------------------------------------------------------------------*\
  2. ====================================
  3. y_timers - Run timers efficiently.
  4. ====================================
  5. Description:
  6. Sets up repeating timers without requiring any SetTimers and arranges them
  7. so that they will be very unlikely to meet (at least for a long time) using
  8. scheduling algorithms to get timers with the same period to be offset. Also
  9. fixes arrays and strings in timers so they can be passed properly.
  10. Legal:
  11. Version: MPL 1.1
  12. The contents of this file are subject to the Mozilla Public License Version
  13. 1.1 (the "License"); you may not use this file except in compliance with
  14. the License. You may obtain a copy of the License at
  15. http://www.mozilla.org/MPL/
  16. Software distributed under the License is distributed on an "AS IS" basis,
  17. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  18. for the specific language governing rights and limitations under the
  19. License.
  20. The Original Code is the YSI timers include.
  21. The Initial Developer of the Original Code is Alex "Y_Less" Cole.
  22. Portions created by the Initial Developer are Copyright (C) 2011
  23. the Initial Developer. All Rights Reserved.
  24. Contributors:
  25. ZeeX, koolk, JoeBullet/Google63, g_aSlice/Slice
  26. Thanks:
  27. JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
  28. ZeeX - Very productive conversations.
  29. koolk - IsPlayerinAreaEx code.
  30. TheAlpha - Danish translation.
  31. breadfish - German translation.
  32. Fireburn - Dutch translation.
  33. yom - French translation.
  34. 50p - Polish translation.
  35. Zamaroht - Spanish translation.
  36. Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes
  37. for me to strive to better.
  38. Pixels^ - Running XScripters where the idea was born.
  39. Matite - Pestering me to release it and using it.
  40. Very special thanks to:
  41. Thiadmer - PAWN, whose limits continue to amaze me!
  42. Kye/Kalcor - SA:MP.
  43. SA:MP Team past, present and future - SA:MP.
  44. Version:
  45. 2.0
  46. Changelog:
  47. 29/04/11:
  48. Added version 2 of the code with more advanced options.
  49. 21/03/11:
  50. Added debug printing to timer functions. Uses "P:C" in compiling.
  51. 26/10/10:
  52. Officially added simple calling.
  53. Added "delay" functions.
  54. 12/10/10:
  55. Rewrote for YSI 1.0 using y_scripting.
  56. 11/08/07:
  57. Removed millions of defines to reduce pre-processing.
  58. Added pickups.
  59. 03/08/07:
  60. First version.
  61. \*----------------------------------------------------------------------------*/
  62. // Disable this version!
  63. #if defined USE_Y_TIMERS_V1
  64. #include "internal\y_timersold"
  65. #endinput
  66. #endif
  67. #include "internal\y_version"
  68. #include "y_amx"
  69. #include "internal\y_shortfunc"
  70. #include "y_malloc"
  71. #include "y_iterate"
  72. // This will be called twice and means there are two separate OnScriptInit
  73. // functions in this file.
  74. #include "y_hooks"
  75. #include "y_debug"
  76. #define task%0[%1](%2) %0@yT_(g,p);public%0@yT_(g,p){static s=-1;return _Timer_I(#%0,%1,g,s);}%0();public%0()
  77. #define ptask%0[%1](%2) stock %0_yT@=1;%0@yT_(g,p);public%0@yT_(g,p){static s[MAX_PLAYERS]={-1,...},a[2];return _Timer_D(#%0@_yT,%1,g,p,s,%0_yT@,a);}%0@_yT(p);public%0@_yT(p)if(%0_yT@)%0(p);%0(%2)
  78. #define pause%0; {J@=_:@Ym:%0@yT_(0,-1);}
  79. #define resume%0; {J@=_:@Ym:%0@yT_(1,-1);}
  80. #define @Ym:%0:%1@yT_(%2,-1) %0@yT_(%2,%1)
  81. static stock
  82. Alloc:YSI_g_sLastSlot = NO_ALLOC,
  83. Alloc:YSI_g_sFirstSlot = NO_ALLOC,
  84. YSI_g_sPlayerTimers = -1;
  85. hook OnScriptInit()
  86. {
  87. P:1("hook Timers_OnScriptInit called");
  88. new
  89. pointer,
  90. time,
  91. idx,
  92. entry;
  93. while ((idx = AMX_GetPublicEntrySuffix(idx, entry, _A<@yT_>)))
  94. //while ((idx = AMX_GetPublicPointerSuffix(idx, pointer, _A<@yT_>)))
  95. {
  96. P:6("Timer_OnScriptInit: entry: %d", entry);
  97. #emit LREF.S.pri entry
  98. #emit STOR.S.pri pointer
  99. //YSI_g_sCurFunc = pointer;
  100. // Don't bother with the real name, call the function by address to get
  101. // the time the function runs for.
  102. P:7("Timer_OnScriptInit: pointer: %d", pointer);
  103. // Push the address of the current function.
  104. #emit PUSH.S pointer
  105. #emit PUSH.C 0xFFFFFFFF
  106. #emit PUSH.C 8
  107. #emit LCTRL 6
  108. #emit ADD.C 28
  109. #emit PUSH.pri
  110. #emit LOAD.S.pri pointer
  111. #emit SCTRL 6
  112. #emit STOR.S.pri time
  113. //YSI_g_sCurFunc = 0;
  114. P:7("Timer_OnScriptInit: time: %d", time);
  115. if (time != -1)
  116. {
  117. // Find all the functions with the same time. This is less
  118. // efficient than previous implementations (it is O(N^2)), but also
  119. // more robust as it won't fail no matter how many different times
  120. // there are - old ones relied on an array with a finite size.
  121. new
  122. pointer2,
  123. time2,
  124. idx2,
  125. total,
  126. pre;
  127. while ((idx2 = AMX_GetPublicPointerSuffix(idx2, pointer2, _A<@yT_>)))
  128. {
  129. // Call the functions a second time to guarantee getting
  130. #emit PUSH.C 0
  131. #emit PUSH.C 0xFFFFFFFF
  132. #emit PUSH.C 8
  133. #emit LCTRL 6
  134. #emit ADD.C 28
  135. #emit PUSH.pri
  136. #emit LOAD.S.pri pointer2
  137. #emit SCTRL 6
  138. #emit STOR.S.pri time2
  139. if (time2 == time)
  140. {
  141. ++total;
  142. if (idx2 < idx)
  143. {
  144. ++pre;
  145. }
  146. }
  147. }
  148. P:7("Timer_OnScriptInit: total: %d, time: %d, pre: %d", total, time, pre);
  149. // Now we know what time this function has, how many others have
  150. // that time and how many have already been started.
  151. new
  152. buffer[32];
  153. entry += 4;
  154. #emit LREF.S.pri entry
  155. #emit STOR.S.pri pointer
  156. AMX_ReadString(AMX_BASE_ADDRESS + pointer, buffer);
  157. P:7("Timer_OnScriptInit: %s", unpack(buffer));
  158. // Get the time offset for the current call. This should mean that
  159. // all the functions are nicely spread out.
  160. SetTimerEx(buffer, time * pre / total, 0, "ii", 1, -1);
  161. }
  162. }
  163. P:1("hook Timers_OnScriptInit ended");
  164. }
  165. hook OnPlayerConnect(playerid)
  166. {
  167. P:1("hook Timers_OnPlayerConnect called: %d", playerid);
  168. // Loop through all the per-player timers. Correctly finds them all from a
  169. // linked list hidden in static variables (which are really global).
  170. new
  171. cur = YSI_g_sPlayerTimers,
  172. data;
  173. while (cur != -1)
  174. {
  175. #emit LREF.S.pri cur
  176. #emit STOR.S.pri data
  177. P:6("Timers_OnPlayerConnect: func: %x", data);
  178. // Start this timer for this player.
  179. #emit PUSH.S playerid
  180. #emit PUSH.C 1
  181. // Push the parameter count (in bytes). This is actually passed to
  182. // native functions directly.
  183. #emit PUSH.C 8
  184. // Call the function currently in the list to trigger the repeating
  185. // timer. This involves getting the current "cip" address, modifying it
  186. // to get the return address then modifying "cip" to call the function.
  187. #emit LCTRL 6
  188. #emit ADD.C 28
  189. #emit PUSH.pri
  190. #emit LOAD.S.pri data
  191. #emit SCTRL 6
  192. // Returned, get the next list element.
  193. cur += 4;
  194. #emit LREF.S.pri cur
  195. #emit STOR.S.pri cur
  196. }
  197. P:1("hook Timers_OnPlayerConnect ended");
  198. }
  199. hook OnPlayerDisconnect(playerid, reason)
  200. {
  201. P:1("hook Timers_OnPlayerDisconnect called: %d, %d, playerid, reason");
  202. // Loop through all the per-player timers. Correctly finds them all from a
  203. // linked list hidden in static variables (which are really global).
  204. new
  205. cur = YSI_g_sPlayerTimers,
  206. data;
  207. while (cur != -1)
  208. {
  209. #emit LREF.S.pri cur
  210. #emit STOR.S.pri data
  211. P:6("Timers_OnPlayerDisconnect: func: %x", data);
  212. // End this timer for this player.
  213. #emit PUSH.S playerid
  214. #emit PUSH.C 0
  215. // Push the parameter count (in bytes). This is actually passed to
  216. // native functions directly.
  217. #emit PUSH.C 8
  218. // Call the function currently in the list to trigger the repeating
  219. // timer. This involves getting the current "cip" address, modifying it
  220. // to get the return address then modifying "cip" to call the function.
  221. #emit LCTRL 6
  222. #emit ADD.C 28
  223. #emit PUSH.pri
  224. #emit LOAD.S.pri data
  225. #emit SCTRL 6
  226. // Returned, get the next list element.
  227. cur += 4;
  228. #emit LREF.S.pri cur
  229. #emit STOR.S.pri cur
  230. }
  231. P:1("hook Timers_OnPlayerDisconnect ended");
  232. }
  233. stock _Timer_I(func[], interval, action, &result)
  234. {
  235. P:3("_Timer_I called");
  236. switch (action)
  237. {
  238. case 0:
  239. {
  240. if (result != -1)
  241. {
  242. KillTimer(result),
  243. result =- 1;
  244. }
  245. }
  246. case 1:
  247. {
  248. if (result == -1)
  249. {
  250. result = SetTimer(func, interval, 1);
  251. }
  252. }
  253. }
  254. return interval;
  255. }
  256. // Attempt to stop or start a task, possibly for a single player.
  257. stock _Timer_D(func[], interval, const action, who, results[MAX_PLAYERS], &pp, a[2])
  258. {
  259. P:3("_Timer_D called");
  260. switch (action)
  261. {
  262. case -1:
  263. {
  264. if (who)
  265. {
  266. a[0] = who;
  267. a[1] = YSI_g_sPlayerTimers;
  268. // Store the address of the global array.
  269. #emit LOAD.S.pri a
  270. #emit STOR.pri YSI_g_sPlayerTimers
  271. }
  272. }
  273. case 0:
  274. {
  275. // Stop the timer.
  276. if (who == -1)
  277. {
  278. pp = 0;
  279. }
  280. else if (results[who] != -1)
  281. {
  282. KillTimer(results[who]);
  283. results[who] = -1;
  284. }
  285. }
  286. case 1:
  287. {
  288. // Start the timer.
  289. if (who == -1)
  290. {
  291. pp = 1;
  292. }
  293. else if (results[who] == -1)
  294. {
  295. results[who] = SetTimerEx(func, interval, true, "i", who);
  296. }
  297. }
  298. }
  299. // No global interval for per-player timers.
  300. return -1;
  301. }
  302. static stock Alloc:Timer_GetSingleSlot(len)
  303. {
  304. // Allocates memory and secretly appends data to the start.
  305. P:4("Timer_GetSingleSlot called: %d", len);
  306. new
  307. Alloc:slot = malloc(len + 1);
  308. if (slot == NO_ALLOC)
  309. {
  310. return NO_ALLOC;
  311. }
  312. P:5("Timer_GetSingleSlot: %d, %d, %d", _:YSI_g_sFirstSlot, _:YSI_g_sLastSlot, _:slot);
  313. // Standard linked list.
  314. if (YSI_g_sFirstSlot == NO_ALLOC)
  315. {
  316. YSI_g_sFirstSlot = slot;
  317. }
  318. else
  319. {
  320. mset(YSI_g_sLastSlot, 0, _:slot);
  321. }
  322. YSI_g_sLastSlot = slot;
  323. mset(YSI_g_sLastSlot, 0, -1);
  324. return slot;// + Alloc:1;
  325. }
  326. // Allocate memory to store a string.
  327. stock _Timer_S(string:str[])
  328. {
  329. P:3("_Timer_S called");
  330. new
  331. len = strlen(str);
  332. if (len & 0x0F)
  333. {
  334. len = (len & ~0x0F) + 32;
  335. }
  336. new
  337. Alloc:slot = Timer_GetSingleSlot(len + 1);
  338. if (slot != NO_ALLOC)
  339. {
  340. msets(slot, 1, str);
  341. }
  342. P:5("str: %d", _:slot);
  343. return _:slot + 1;
  344. }
  345. // Allocate memory to store an array.
  346. stock _Timer_A(str[], len)
  347. {
  348. P:3("_Timer_A called");
  349. new
  350. Alloc:slot = Timer_GetSingleSlot(len);
  351. if (slot != NO_ALLOC)
  352. {
  353. mseta(slot, 1, str, len);
  354. }
  355. P:5("str: %d", _:slot);
  356. return _:slot + 1;
  357. }
  358. //stock
  359. // I@ = -1;
  360. // Create the timer setup.
  361. stock _Timer_C(tt, g)
  362. {
  363. P:3("_Timer_C called: %d, %d", tt, g);
  364. //P:3("_Timer_C called: %d", tt);
  365. // This is done here for convenience.
  366. I@ = -1;
  367. // Only repeating timers are freed like this.
  368. // UPDATE: Now all timers with array parameters, regardless of repeat status
  369. // are freed like this. Only timers with no malloc aren't.
  370. if (g)
  371. {
  372. new
  373. Alloc:slot = Timer_GetSingleSlot(1);
  374. P:5("_Timer_C: slot = %d", _:slot);
  375. if (slot == NO_ALLOC)
  376. {
  377. // Not a graceful fail!
  378. return 0;
  379. }
  380. mset(slot, 1, tt);
  381. // Just so it isn't a real timer ID (or possibly isn't).
  382. slot = ~YSI_g_sFirstSlot;// ^ Alloc:-1;
  383. YSI_g_sFirstSlot = NO_ALLOC;
  384. YSI_g_sLastSlot = NO_ALLOC;
  385. return _:slot;
  386. }
  387. // Reset these variables on all timers, including self-cleaning ones.
  388. YSI_g_sFirstSlot = NO_ALLOC;
  389. YSI_g_sLastSlot = NO_ALLOC;
  390. return tt;
  391. }
  392. // Free all timer resources.
  393. stock _Timer_F(slot)
  394. {
  395. P:3("_Timer_F called");
  396. // This is done here for convenience.
  397. if (slot & 0x80000000)
  398. {
  399. new
  400. next;
  401. slot = ~slot; //^= -1;
  402. for ( ; ; )
  403. {
  404. next = mget(Alloc:slot, 0);
  405. P:6("_Timer_F: slot = %d, next = %d", slot, next);
  406. // Out of stored strings and arrays.
  407. if (next == -1)
  408. {
  409. KillTimer(mget(Alloc:slot, 1));
  410. free(Alloc:slot);
  411. break;
  412. }
  413. free(Alloc:slot);
  414. slot = next;
  415. }
  416. }
  417. else
  418. {
  419. KillTimer(slot);
  420. }
  421. return 1;
  422. }
  423. stock _Timer_H(slot)
  424. {
  425. _Timer_F(~(slot - 1));
  426. }
  427. // These are the tag-type definitions for the various possible parameter types.
  428. // Array-like definitions.
  429. #define @Yf:@Yg:@Yh:#%0#%1|||%3[%4]|||%5,%6;%8>%9||| @Ye:@Yw:#%0#%1|||%3[%4]|||%5,%6;%9|||
  430. #define @Ye:@Yw:#%0#%1|||%2string:%3[%4]|||%5,%6;%9||| @Yf:@Yg:@Yh:#%0d#%1,_Timer_S(%3)|||%5|||%6;1>%9,@Yv&%3&|||
  431. // This one has an extra parameter because arrays must always be followed by
  432. // their length.
  433. #define @Yw:#%0#%1|||%3[%4]|||%5,%6,%7;%9||| @Yf:@Yg:@Yh:#%0d#%1,_Timer_A(%3,%5)|||%5|||%6,%7;1>%9,@Yv&%3&|||
  434. // Tag-like definitions.
  435. #define @Yg:@Yh:#%0#%1|||%2:%3|||%5,%6;%8>%9||| @Yf:@Yg:@Yh:#%0d#%1,%2:%3|||%5|||%6;%8>%9,%2:%3|||
  436. // Others.
  437. #define @Yh:#%0#%1|||%3|||%5,%6;%8>%9||| @Yf:@Yg:@Yh:#%0d#%1,%3|||%5|||%6;%8>%9,%3|||
  438. // Main entry point for defer type determination.
  439. #define _YT@CR:%0,%1)%8>%2||| @Yf:@Yg:@Yh:#i#,@Yx:J@|||%0|||%1;%8>%2|||
  440. // Define for "defer" with timer, parameters and main function.
  441. #define YSI_timer%0[%1]%3(%2) stock%0_yT@(%2)return _Timer_C(O@(#%0@_yT,(I@==-1)?(%1):I@,J@,_YT@CR:%2,,)0>%0|||%0|||(%2)
  442. // Expand additional parameters out to three functions after processing.
  443. //#define @Yx:%0||||||;%1,%2|||%4||| %0),1);@Yu:%1@_yT(%2);public @Yu:%1@_yT(%2){%4(%2);}%4
  444. #define @Yx:%0||||||;%8>%1,%2|||%4||| %0),%8);@Yj:%1@_yT(__r,%2);public @Yj:%1@_yT(__r,%2){%4(%2);}%4
  445. // Can't believe I never thought of this before! If only there was a way to
  446. // make it less generic than "id".
  447. #define id#,@Yx:J@,||||||;%8>%1,%2|||%4||| ),0);%1@_yT();public%1@_yT(){%4();}%4
  448. // Remove excess "_Timer_G" and "_Timer_B".
  449. #define @Yj:%0(%5){%1(%8&%2&%3)%9} @Yj:%0(%5){%1(%8:YSI_gMallocMemory[%2]%3);@Yl(@Yk:%2)%9}
  450. //#define @Yu:%0{%1(%8&%2&%3)%9} @Yu:%0{%1(%8:YSI_gMallocMemory[%2]%3)%9}
  451. #define @Yv&%0& %0
  452. //#define @Yk:%0);@Yl(@Yk:%1); %0,@Yk:%1);
  453. // I'm not entirely sure why this works in reverse order, but I'm glad it does!
  454. #define @Yk:%0);@Yl(@Yk:%1); @Yk:%0);
  455. #define @Yl if(!__r)_Timer_F
  456. //#define string:
  457. #define defer%0(%1) (J@=0,I@=-1,Timer:%0_yT@(%1))
  458. #define repeat%0(%1) (J@=1,I@=-1,Timer:%0_yT@(%1))
  459. // Custom time.
  460. #define Timer:%0[%1]_yT@(%2) I@=%1,Timer:%0_yT@(%2)
  461. #define stop%0; {_Timer_F(_:%0);}
  462. #define timerfunc YSI_timer
  463. #if !defined YSI_NO_timer
  464. #define timer YSI_timer
  465. #endif