impl.inc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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. </remarks>
  62. \**--------------------------------------------------------------------------**/
  63. // Disable this version!
  64. static stock
  65. Alloc:YSI_g_sLastSlot = NO_ALLOC,
  66. Alloc:YSI_g_sFirstSlot = NO_ALLOC,
  67. YSI_g_sPlayerTimers = -1;
  68. hook OnScriptInit()
  69. {
  70. P:1("hook Timers_OnScriptInit called");
  71. new
  72. pointer,
  73. time,
  74. idx,
  75. entry;
  76. while ((idx = AMX_GetPublicEntryPrefix(idx, entry, _A<@yT_>)))
  77. //while ((idx = AMX_GetPublicPointerSuffix(idx, pointer, _A<@yT_>)))
  78. {
  79. P:6("Timer_OnScriptInit: entry: %d", entry);
  80. #emit LREF.S.pri entry
  81. #emit STOR.S.pri pointer
  82. //YSI_g_sCurFunc = pointer;
  83. // Don't bother with the real name, call the function by address to get
  84. // the time the function runs for.
  85. P:7("Timer_OnScriptInit: pointer: %d", pointer);
  86. // Push the address of the current function.
  87. #emit PUSH.S pointer
  88. #emit PUSH.C 0xFFFFFFFF
  89. #emit PUSH.C 8
  90. #emit LCTRL 6
  91. #emit ADD.C 28
  92. #emit PUSH.pri
  93. #emit LOAD.S.pri pointer
  94. #emit SCTRL 6
  95. #emit STOR.S.pri time
  96. //YSI_g_sCurFunc = 0;
  97. P:7("Timer_OnScriptInit: time: %d", time);
  98. if (time != -1)
  99. {
  100. // Find all the functions with the same time. This is less
  101. // efficient than previous implementations (it is O(N^2)), but also
  102. // more robust as it won't fail no matter how many different times
  103. // there are - old ones relied on an array with a finite size.
  104. new
  105. pointer2,
  106. time2,
  107. idx2,
  108. total,
  109. pre;
  110. while ((idx2 = AMX_GetPublicPointerPrefix(idx2, pointer2, _A<@yT_>)))
  111. {
  112. // Call the functions a second time to guarantee getting
  113. #emit PUSH.C 0
  114. #emit PUSH.C 0xFFFFFFFF
  115. #emit PUSH.C 8
  116. #emit LCTRL 6
  117. #emit ADD.C 28
  118. #emit PUSH.pri
  119. #emit LOAD.S.pri pointer2
  120. #emit SCTRL 6
  121. #emit STOR.S.pri time2
  122. // Check if the new time is a FACTOR, SAME, or MULTIPLE of this
  123. // task, so we don't start different timers together.
  124. if (time2 == time || time / time2 * time2 == time || time2 / time * time == time2)
  125. {
  126. ++total;
  127. if (idx2 < idx)
  128. {
  129. ++pre;
  130. }
  131. }
  132. }
  133. P:7("Timer_OnScriptInit: total: %d, time: %d, pre: %d", total, time, pre);
  134. // Now we know what time this function has, how many others have
  135. // that time and how many have already been started.
  136. new
  137. buffer[32];
  138. entry += 4;
  139. #emit LREF.S.pri entry
  140. #emit STOR.S.pri pointer
  141. AMX_ReadString(AMX_BASE_ADDRESS + pointer, buffer);
  142. P:7("Timer_OnScriptInit: %s", unpack(buffer));
  143. // Get the time offset for the current call. This should mean that
  144. // all the functions are nicely spread out.
  145. SetTimerEx(buffer, time * pre / total, 0, "ii", 1, -1);
  146. }
  147. }
  148. P:1("hook Timers_OnScriptInit ended");
  149. return 1;
  150. }
  151. hook OnPlayerConnect(playerid)
  152. {
  153. P:1("hook Timers_OnPlayerConnect called: %d", playerid);
  154. // Loop through all the per-player timers. Correctly finds them all from a
  155. // linked list hidden in static variables (which are really global).
  156. new
  157. cur = YSI_g_sPlayerTimers,
  158. data;
  159. while (cur != -1)
  160. {
  161. #emit LREF.S.pri cur
  162. #emit STOR.S.pri data
  163. P:6("Timers_OnPlayerConnect: func: %x", data);
  164. // Start this timer for this player.
  165. #emit PUSH.S playerid
  166. #emit PUSH.C 1
  167. // Push the parameter count (in bytes). This is actually passed to
  168. // native functions directly.
  169. #emit PUSH.C 8
  170. // Call the function currently in the list to trigger the repeating
  171. // timer. This involves getting the current "cip" address, modifying it
  172. // to get the return address then modifying "cip" to call the function.
  173. #emit LCTRL 6
  174. #emit ADD.C 28
  175. #emit PUSH.pri
  176. #emit LOAD.S.pri data
  177. #emit SCTRL 6
  178. // Returned, get the next list element.
  179. cur += 4;
  180. #emit LREF.S.pri cur
  181. #emit STOR.S.pri cur
  182. }
  183. P:1("hook Timers_OnPlayerConnect ended");
  184. return 1;
  185. }
  186. hook OnPlayerDisconnect(playerid, reason)
  187. {
  188. P:1("hook Timers_OnPlayerDisconnect called: %d, %d, playerid, reason");
  189. // Loop through all the per-player timers. Correctly finds them all from a
  190. // linked list hidden in static variables (which are really global).
  191. new
  192. cur = YSI_g_sPlayerTimers,
  193. data;
  194. while (cur != -1)
  195. {
  196. #emit LREF.S.pri cur
  197. #emit STOR.S.pri data
  198. P:6("Timers_OnPlayerDisconnect: func: %x", data);
  199. // End this timer for this player.
  200. #emit PUSH.S playerid
  201. #emit PUSH.C 0
  202. // Push the parameter count (in bytes). This is actually passed to
  203. // native functions directly.
  204. #emit PUSH.C 8
  205. // Call the function currently in the list to trigger the repeating
  206. // timer. This involves getting the current "cip" address, modifying it
  207. // to get the return address then modifying "cip" to call the function.
  208. #emit LCTRL 6
  209. #emit ADD.C 28
  210. #emit PUSH.pri
  211. #emit LOAD.S.pri data
  212. #emit SCTRL 6
  213. // Returned, get the next list element.
  214. cur += 4;
  215. #emit LREF.S.pri cur
  216. #emit STOR.S.pri cur
  217. }
  218. P:1("hook Timers_OnPlayerDisconnect ended");
  219. return 1;
  220. }
  221. stock _Timer_I(func[], interval, action, &result)
  222. {
  223. P:3("_Timer_I called");
  224. switch (action)
  225. {
  226. case 0:
  227. {
  228. if (result != -1)
  229. {
  230. KillTimer(result),
  231. result =- 1;
  232. }
  233. }
  234. case 1:
  235. {
  236. if (result == -1)
  237. {
  238. result = SetTimer(func, interval, 1);
  239. }
  240. }
  241. }
  242. return interval;
  243. }
  244. // Attempt to stop or start a task, possibly for a single player.
  245. stock _Timer_D(func[], interval, const action, who, results[MAX_PLAYERS], a[2])
  246. {
  247. P:3("_Timer_D called");
  248. switch (action)
  249. {
  250. case -1:
  251. {
  252. if (who)
  253. {
  254. // Add this timer to the global linked list.
  255. a[0] = who;
  256. a[1] = YSI_g_sPlayerTimers;
  257. // Store the address of the global array.
  258. #emit LOAD.S.pri a
  259. #emit STOR.pri YSI_g_sPlayerTimers
  260. }
  261. }
  262. case 0:
  263. {
  264. // Stop the timer.
  265. if (who == -1)
  266. {
  267. foreach (who : Player)
  268. {
  269. if (results[who] != -1)
  270. {
  271. KillTimer(results[who]);
  272. results[who] = -1;
  273. }
  274. }
  275. }
  276. else if (results[who] != -1)
  277. {
  278. KillTimer(results[who]);
  279. results[who] = -1;
  280. }
  281. }
  282. case 1:
  283. {
  284. // Start the timer.
  285. if (who == -1)
  286. {
  287. foreach (who : Player)
  288. {
  289. if (results[who] == -1)
  290. {
  291. results[who] = SetTimerEx(func, interval, true, "i", who);
  292. }
  293. }
  294. }
  295. else if (results[who] == -1)
  296. {
  297. results[who] = SetTimerEx(func, interval, true, "i", who);
  298. }
  299. }
  300. }
  301. // No global interval for per-player timers.
  302. return -1;
  303. }
  304. static stock Alloc:Timer_GetSingleSlot(len)
  305. {
  306. // Allocates memory and secretly appends data to the start.
  307. P:4("Timer_GetSingleSlot called: %d", len);
  308. new
  309. Alloc:slot = malloc(len + 1);
  310. if (slot == NO_ALLOC)
  311. {
  312. return NO_ALLOC;
  313. }
  314. P:5("Timer_GetSingleSlot: %d, %d, %d", _:YSI_g_sFirstSlot, _:YSI_g_sLastSlot, _:slot);
  315. // Standard linked list.
  316. if (YSI_g_sFirstSlot == NO_ALLOC)
  317. {
  318. YSI_g_sFirstSlot = slot;
  319. }
  320. else
  321. {
  322. mset(YSI_g_sLastSlot, 0, _:slot);
  323. }
  324. YSI_g_sLastSlot = slot;
  325. mset(YSI_g_sLastSlot, 0, -1);
  326. return slot;// + Alloc:1;
  327. }
  328. // Allocate memory to store a string.
  329. stock _Timer_S(string:str[])
  330. {
  331. P:3("_Timer_S called");
  332. new
  333. len = strlen(str);
  334. if (len & 0x0F)
  335. {
  336. len = (len & ~0x0F) + 32;
  337. }
  338. new
  339. Alloc:slot = Timer_GetSingleSlot(len + 1);
  340. if (slot != NO_ALLOC)
  341. {
  342. msets(slot, 1, str);
  343. }
  344. P:5("str: %d", _:slot);
  345. return _:slot + 1;
  346. }
  347. // Allocate memory to store an array.
  348. stock _Timer_A(str[], len)
  349. {
  350. P:3("_Timer_A called");
  351. new
  352. Alloc:slot = Timer_GetSingleSlot(len);
  353. if (slot != NO_ALLOC)
  354. {
  355. mseta(slot, 1, str, len);
  356. }
  357. P:5("str: %d", _:slot);
  358. return _:slot + 1;
  359. }
  360. //stock
  361. // I@ = -1;
  362. // Create the timer setup.
  363. stock _Timer_C(tt, g)
  364. {
  365. P:3("_Timer_C called: %d, %d", tt, g);
  366. //P:3("_Timer_C called: %d", tt);
  367. // This is done here for convenience.
  368. I@ = -1;
  369. // Only repeating timers are freed like this.
  370. // UPDATE: Now all timers with array parameters, regardless of repeat status
  371. // are freed like this. Only timers with no malloc aren't.
  372. if (g)
  373. {
  374. new
  375. Alloc:slot = Timer_GetSingleSlot(1);
  376. P:5("_Timer_C: slot = %d", _:slot);
  377. if (slot == NO_ALLOC)
  378. {
  379. // Not a graceful fail!
  380. return 0;
  381. }
  382. mset(slot, 1, tt);
  383. // Just so it isn't a real timer ID (or possibly isn't).
  384. slot = ~YSI_g_sFirstSlot;// ^ Alloc:-1;
  385. YSI_g_sFirstSlot = NO_ALLOC;
  386. YSI_g_sLastSlot = NO_ALLOC;
  387. return _:slot;
  388. }
  389. // Reset these variables on all timers, including self-cleaning ones.
  390. YSI_g_sFirstSlot = NO_ALLOC;
  391. YSI_g_sLastSlot = NO_ALLOC;
  392. return tt;
  393. }
  394. // Free all timer resources.
  395. stock _Timer_F(slot)
  396. {
  397. P:3("_Timer_F called");
  398. // This is done here for convenience.
  399. if (slot & 0x80000000)
  400. {
  401. new
  402. next;
  403. slot = ~slot; //^= -1;
  404. for ( ; ; )
  405. {
  406. next = mget(Alloc:slot, 0);
  407. P:6("_Timer_F: slot = %d, next = %d", slot, next);
  408. // Out of stored strings and arrays.
  409. if (next == -1)
  410. {
  411. KillTimer(mget(Alloc:slot, 1));
  412. free(Alloc:slot);
  413. break;
  414. }
  415. free(Alloc:slot);
  416. slot = next;
  417. }
  418. }
  419. else
  420. {
  421. KillTimer(slot);
  422. }
  423. return 1;
  424. }
  425. stock _Timer_H(slot)
  426. {
  427. _Timer_F(~(slot - 1));
  428. }
  429. #define task%0[%1](%2) @yT_%0(g,p);@yT_%0(g,p){static s=-1;return _Timer_I(#%0,%1,g,s);}%0();public%0()
  430. //
  431. #define ptask%0[%1](%2) @yT_%0(g,p);@yT_%0(g,p){static s[MAX_PLAYERS]={-1,...},a[2];return _Timer_D(#%0,%1,g,p,s,a);}%0(%2);public%0(%2)
  432. #define @yT_%0\32;%1(%2) @yT_%0%1(%2)
  433. //#define @_yT%0;\32%1(%2) @_yT%0%1(%2)
  434. #define pause%0; {J@=_:@Ym:@yT_%0(0,-1);}
  435. #define resume%0; {J@=_:@Ym:@yT_%0(1,-1);}
  436. #define @Ym:%0[%1](%2,-1) %0(%2,%1)
  437. #define timerfunc YSI_timer
  438. #if !defined YSI_NO_timer
  439. #define timer YSI_timer
  440. #endif