| 04-25-2008, 02:54 AM | #1 |
Features: - Ridicules vJass syntax by exploiting it to the max. - Requires 2 textmacros from user. So it is extremely unfriendly. - Only works when delay time is constant - Can only pass one integer variable, although if you implement the method manually instead of using textmacros you can pass a lot more. - Pending name, will change it someday. - It's low level, if you want to make your code look ugly, this is the way! - cannot use return inside an AwesomeTimeEvent block (Really, don't do it) Disadvantages: - Uses only 2 timers and is MUI, instead of all those superior systems that require hundreds of timers. Or those that require thousands... - No function calls at all during timer expire + it only uses O(1) array operations, comparisons . Unlike other systems that require at least an H2I function call or use logarithmic evaluation time or interfaces ( TriggerEvaluate) Thanks to Strilanc for reminding me that I don't need Mod for circular queues. script:library AwesomeTimeEvent initializer init globals public timer NOW endglobals private function init takes nothing returns nothing set NOW=CreateTimer() call TimerStart(NOW,1000000000,false,null) endfunction endlibrary // please run inside a scope/library waittime must be constant, it allows recursion if you want periodic garbage... //! textmacro AwesomeTimeEvent_Begin takes name, max, waittime private struct $name$[8190] private static timer T public static constant integer MAX=$max$ public static constant real WAIT_TIME = $waittime$ private static $name$ begin=0 private static $name$ end=0 private integer v private real expire private static method onexpire takes nothing returns $name$ local integer value=.begin.v //please don't use return inside AwesomeTimeEvent blocks... //! endtextmacro //! textmacro AwesomeTimeEvent_End takes name set .begin=$name$(integer(.begin)+1) if (.begin==.MAX) then set .begin=0 endif if (.begin!=.end) then call TimerStart(.T,.begin.expire-TimerGetElapsed(AwesomeTimeEvent_NOW),false,function $name$.onexpire) endif return 0 endmethod public static method push takes integer value returns nothing set .end.v = value set .end.expire = TimerGetElapsed(AwesomeTimeEvent_NOW)+.WAIT_TIME set .end = $name$( integer(.end)+1) if( integer(.end)==.MAX) then set .end=0 endif if (.begin==.end) then debug call BJDebugMsg("Warning: \"$name$\" Time Event queue is full, forcing an expire") call .onexpire() else call TimerStart(.T,.begin.expire-TimerGetElapsed(AwesomeTimeEvent_NOW),false,function $name$.onexpire) endif endmethod private static method onInit takes nothing returns nothing set .T=CreateTimer() endmethod private static method create takes nothing returns $name$ return 0 //surprise! endmethod endstruct //! endtextmacro sampleusage:library awesometest initializer init //! runtextmacro AwesomeTimeEvent_Begin("message","8190","4.0") call BJDebugMsg(R2S(TimerGetElapsed(AwesomeTimeEvent_NOW) )+" pop "+I2S(value)) //! runtextmacro AwesomeTimeEvent_End("message") //! runtextmacro AwesomeTimeEvent_Begin("messagehalf","8190","2.0") call BJDebugMsg(R2S(TimerGetElapsed(AwesomeTimeEvent_NOW) )+" #pop "+I2S(value)) //! runtextmacro AwesomeTimeEvent_End("messagehalf") private function init takes nothing returns nothing local integer x local integer n loop call TriggerSleepAction(1) set n=GetRandomInt(1,2) loop set x=GetRandomInt(0,1000) call BJDebugMsg(R2S(TimerGetElapsed(AwesomeTimeEvent_NOW) )+" pushback "+I2S(x)) call message.push(x) call messagehalf.push(x) set n=n-1 exitwhen (n==0) endloop endloop endfunction endlibrary |
| 04-25-2008, 04:30 AM | #2 |
So... how do we use this thing O_o |
| 04-25-2008, 05:51 AM | #3 |
It looks sortof like a call queue, except the calls are spaced by some constant amount if you queue them too fast. |
| 04-25-2008, 06:17 AM | #4 |
Awesome idea, but it won't be a true system until:
I have even come up with a benchmarking script for you: JASS://! textmacro UberBenchmark takes NAME library $NAME$ // Call $NAME$_Run() to run the benchmark. public function Run takes nothing returns nothing local integer execs if ("$NAME$" == "AwesomeTimeEvent") then set execs = 1337 else set execs = GetRandomInt(20, 100) endif call BJDebugMsg("$NAME$: "+I2S(execs)+" executions per millisecond.") endfunction endlibrary //! endtextmacro //! runtextmacro UberBenchmark("AwesomeTimeEvent") //! runtextmacro UberBenchmark("CompetingSystem") NB: Preceding text is intended purely as satire. |
| 04-25-2008, 02:08 PM | #5 | |
Quote:
2. It's easy as long as you got an screwed up mind JASS:// This library creates a special effect and destroys it after 14 seconds, this is not the // best application for AwesomeTimeEvent because of the constant timeout, but it should // be good for explaining how to use it library TimedFX //you must run the textmacro inside a scope or library. // private struct data effect fx endstruct //begin TimeEvent handling block // [name of time event] [instance limit] [timeout] //! runtextmacro AwesomeTimeEvent_Begin("DoDestroyEffect" ,"8190" ,"14.0") //inside this block you can use the integer argument 'value' to recognize which //event just fired local data D=data(value) //notice we can use our own locals call DestroyEffect(D.fx) set D.fx=null call D.destroy() //! runtextmacro AwesomeTimeEvent_End("DoDestroyEffect") function TimedFX takes string fxpath, real x, real y returns nothing local data D=data.create() set D.fx = AddSpecialEffect(fxpath,x,y) //we use push to create a new instance of the TimeEvent call DoDestroyEffect.push( integer(D) ) // So, after 14.0 seconds, that 'function' above will execute with // the index of the instance as value argument. endfunction endlibrary --- I think I'll remove the waittime argument from the macro and add it to push instead. The rule still remains as "always use constant timeouts" but I think the code will make more sense to more people if push had the timeout. There is also the fact that timeout can be variable, with few conditions: - all active instances should have the same timeout. - all active instances before your push call should timeout before the new action. Anyways, for normal people it should remain as "always use constant timeouts". Edit: I have tried with things like 0.04 seconds. It indeed lags less yet, I have noticed a huge issue, there were less ticks than when you used a normal timer with attachments. It looks to me TimerGetElapsed is not precise, at all. I made an isolated test, a timer that is started at map init and has a long timeout, + another timer that expires every 0.04 seconds, and outputs the elapsed time for the first, the output repeats itself after three tics, it is lame, but that's how it seems to be working. Perhaps it is something blizz made as an optimization, the thing is that TimerGetElapsed is not accurate, thus dooming this system and any other based on TimerGetElapsed into long timeouts (looks like longer than 0.5s is safe) Edit II: In order to test this on 0.04 seconds cases, I did this (lame) JASS:library AwesomeTimeEvent initializer init globals public real NOW=0 private constant real tick =0.001 endglobals private function dot takes nothing returns nothing set NOW=NOW+tick endfunction private function init takes nothing returns nothing call TimerStart(CreateTimer(),tick,true, function dot) endfunction endlibrary Anyways, I test it against a timer that uses this for attachments: JASS:
private function arrg takes nothing returns nothing
local obj fx=xefx( ATTACH[H2I(GetExpiredTimer())-0x100000 ] )
ATTACH is actually a global declared like this: JASS:globals private obj array ATTACH[60000] endglobals So, it is the typical dual function call attach system. Anyways, the results were dissapointing, after 200 instances AwesomeTimers drops fps to 18~ , while the Attach system using simultaneous timers moves it to 24ish. The good news is that I later tested the deal with an array + loop one, in that, after 200 instances the fps is around 30ish... (I moved the camera away to some black area to avoid graphics-caused fps-drop in the equation) From what it looks like this thing is slower than the dual function attach one, that's not true however since I had to use the above library hack to have precision, I am yet to think of a good way to test performance on a setting with longer delays so I don't have to use that lame thing and keep using TimerGetElapsed instead. Edit II: If anyone is wondering, changing the ATTACH array so it got size 8000 (and thus becomes a normal array, and this method is equivalent to the one that preloads 6000 timers) the fps after 200 instances is 28ish instead of 24ish (when we have the function call) array loop still beats it, but it is faster than normal attach systems that use 2 function calls. I just don't get why this time event thing is much slower than other things that use so many funciton calls, but I think that the 0.001 seconds timer helps... Wonder how else could I fix the TimerGetElapsed pecision issue... edit III: Changing tick constant to 0.01 raises fps after 200 instances to 22 ish, still too slow , it also looks like it is not precise enough anymore. Edit IV: If I change ATTACH size to 400000 - thus making it robust as heck - the fps is still 28ish, great news? -- There is something I do not consider when testing attach-based methods, and it is that you are supposed to recycle the timer eventually. -- Edit V: Without that method to increase precision, and using TimerGetElapsed, TimedEvent for periodic 0.04 seconds delay is imprecise as heck, as a matter of fact the peasants moved slower than with the other methods, but the fps is around 30ish which means it is fast, but it is also useless for such low timeouts. :( -- Edit VI: Has anyone seen a map in which handle ids got as large as "17177216"? |
| 04-25-2008, 03:55 PM | #6 |
So, I made a version that allows periodic events and to get rid of the TimerGetElapsed issue using a timer stack instead. No longer needs that library. Still no function calls required. Unfortunately, it uses a timer per instance, which blows. JASS://! textmacro AltAwesomeTimeEvent_Begin takes name, max, waittime private struct $name$[8190] private static constant real WAIT_TIME=$waittime$ public static constant integer MAX=$max$ private static $name$ begin=0 private static $name$ end=0 private integer v private static real next private static timer array V[$max$] private static integer N=0 private static method onexpire takes nothing returns $name$ local integer value=.begin.v local boolean stop=false //please don't use return inside AwesomeTimeEvent blocks... //! endtextmacro //! textmacro AltAwesomeTimeEvent_End takes name set .begin=$name$(integer(.begin)+1) if (.begin==.MAX) then set .begin=0 endif if (not stop) then set .end.v=value set .end=$name$(integer(.end)+1) if(integer(.end)==.MAX) then set .end=0 endif return 0 endif call PauseTimer(GetExpiredTimer()) if(.N==.MAX) then debug call BJDebugMsg("Warning: No space in stack for timer, so we are just letting it go and leak...") else set .V[.N]=GetExpiredTimer() set .N=.N+1 endif return 0 endmethod So, the mystery here is why it drops fps to 25ish, when just using the timers altogether and some attach system that requires a function call is 28ish, I think I need an actual benchmark cause fps seems to be a little ambiguous... |
| 04-25-2008, 09:04 PM | #7 |
very interesting investigations |
