| 04-28-2009, 04:51 AM | #1 |
I'm sure it's small beans compared to the big productions out there, but I needed something lightweight for my map so I worked this up. I'd appreciate any feedback on improving efficiency or general critiques. It's a single timer system that uses a linked list of objects and a few globals to run all timed effects. JASS:globals timeHandler MAIN_TIME real TIME_STEP = .03 real SLOW_STEP = 1.00 real TIME = 0 endglobals //And in the init set MAIN_TIME = timeHandler.create() The setup JASS:library timeHandler requires Handles function mainStepCaller takes nothing returns nothing call MAIN_TIME.mainStep() endfunction struct timeHandler real time timer handler boolean slow = true TMLinkedList objects static method create takes nothing returns timeHandler local timeHandler th = timeHandler.allocate() set th.handler = CreateTimer() set th.objects = TMLinkedList.create() call TimerStart(th.handler, SLOW_STEP, true, function mainStepCaller) return th endmethod method addObj takes timedObj obj returns boolean if(obj!=null) then call .objects.add(obj) //if(LIST) then // call BJDebugMsg("Added "+obj.name+", current timed objs = "+I2S(MAIN_TIME.objects.size)) //endif if .slow then call PauseTimer(MAIN_TIME.handler) call TimerStart(MAIN_TIME.handler, TIME_STEP, true, function mainStepCaller) set .slow = false endif return true else //if(LIST) then // call BJDebugMsg("Attempted to add null to list") //endif endif return false endmethod method removeObj takes timedObj obj returns nothing call .objects.removeObj(obj) //if(LIST) then // call BJDebugMsg("Removed "+obj.name+", current timed objs = "+I2S(MAIN_TIME.objects.size)) //endif if .objects.size == 0 then call PauseTimer(MAIN_TIME.handler) call TimerStart(MAIN_TIME.handler, SLOW_STEP, true, function mainStepCaller) set .slow = true endif endmethod method mainStep takes nothing returns nothing local timedObj head = .objects.head.obj local TMNode pointer = .objects.end local timedObj curr //if DEBUG then // call BJDebugMsg("main step run: "+R2S(TIME)) //endif if .objects.size > 0 then loop set curr = pointer.obj //if NAMES then // call BJDebugMsg(curr.name + " in list") //endif if curr.shouldStep() then call curr.step() endif set curr.duration = curr.duration + TIME_STEP exitwhen curr == head set pointer = pointer.prev endloop else if not(.slow) then call PauseTimer(.handler) call TimerStart(.handler, SLOW_STEP, true, function mainStepCaller) endif endif if .slow then set TIME = TIME + SLOW_STEP else set TIME = TIME + TIME_STEP endif endmethod endstruct endlibrary This is the primary object that does all the work. It has a list of timedObj structs which it cycles through and updates for every timer period, taking the need to manage timers away from the spells. They just add themselves to the timeHandler and get taken care of. JASS:library timedObj requires Handles interface timedObj string name real duration = 0.0 method step takes nothing returns nothing method shouldStep takes nothing returns boolean endinterface endlibrary This is just the interface that spell structs extend JASS:library LinkedList //! textmacro LinkedList takes TYPENAME, TYPE struct $TYPENAME$LinkedList $TYPENAME$Node head $TYPENAME$Node end integer size = 0 static method create takes nothing returns $TYPENAME$LinkedList local $TYPENAME$LinkedList list = $TYPENAME$LinkedList.allocate() return list endmethod method add takes $TYPE$ obj returns nothing if this.size == 0 then set this.head = $TYPENAME$Node.create(obj) set this.end = this.head elseif this.size == 1 then set this.head.next = $TYPENAME$Node.create(obj) set this.head.next.prev = this.head set this.end = this.head.next else set this.end.next = $TYPENAME$Node.create(obj) set this.end.next.prev = this.end set this.end = this.end.next endif set this.size = this.size+1 endmethod method removeEnd takes nothing returns $TYPE$ local $TYPENAME$Node objnode = this.end local $TYPE$ obj set this.end = objnode.prev set this.end.next = 0 set this.size = this.size - 1 set obj = objnode.obj call $TYPENAME$Node.destroy(objnode) return obj endmethod method removeHead takes nothing returns $TYPE$ local $TYPENAME$Node objnode = this.head local $TYPE$ obj set this.head = objnode.next set this.head.prev = 0 set this.size = this.size - 1 set obj = objnode.obj call $TYPENAME$Node.destroy(objnode) return obj endmethod method removeNode takes $TYPENAME$Node match returns boolean local $TYPENAME$Node node = this.head loop if node == match then set match.prev.next = match.next set match.next.prev = match.prev call $TYPENAME$Node.destroy(match) set this.size = this.size - 1 if match == this.head then set this.head = match.next elseif match == this.end then set this.end = match.prev endif return true else if node == this.end then call BJDebugMsg("Couldn't remove object attached to node number "+I2S(match)) return false else set node = node.next endif endif endloop return false endmethod method removeObj takes $TYPE$ match returns boolean local $TYPENAME$Node objnode = this.head local $TYPE$ obj = objnode.obj loop if obj == match then return .removeNode(objnode) else if objnode == this.end then return false else set objnode = objnode.next set obj = objnode.obj endif endif endloop return false endmethod method onDestroy takes nothing returns nothing loop exitwhen this.size == 0 call this.removeEnd() endloop endmethod endstruct struct $TYPENAME$Node $TYPENAME$Node next = 0 $TYPENAME$Node prev = 0 $TYPE$ obj static method create takes $TYPE$ obj returns $TYPENAME$Node local $TYPENAME$Node node = $TYPENAME$Node.allocate() set node.obj = obj return node endmethod endstruct //! endtextmacro //! runtextmacro LinkedList("TM","timedObj") //! runtextmacro LinkedList("UN","unit") endlibrary This is a generic linked list implementation using text macros. I know the removeObj is wonky in that it calls removeNode instead of just removing the node itself, but I was having issues that it seemed to resolve. I think they may have been unrelated though, and I'll probably try changing it back in the future. JASS:library ImageTrail requires MapFunctions globals //Image trail stuff //integer IMAGETRAILSPAWNTIME = 4 //integer IMAGETRAILFADEINTERVAL = 7 real IMAGEDURATION = 1.0928 real IMAGESEPERATION = .1 endglobals struct ImageTrail extends timedObj unit caster boolean on UNLinkedList list real x real y real maxtime player p integer constraint = 0 integer spawntime integer fadeinterval effect blur static method create takes real ImgDur, real ImgSep, real MaxTime, unit u returns ImageTrail local ImageTrail td = ImageTrail.allocate() set td.maxtime = MaxTime set td.spawntime = R2I(ImgSep/TIME_STEP) set td.fadeinterval = R2I((255/ImgDur)*TIME_STEP) set td.caster = u set td.list = UNLinkedList.create() set td.p = GetOwningPlayer(u) set td.on = true set td.x = GetUnitX(u) set td.y = GetUnitY(u) call MAIN_TIME.addObj(td) return td endmethod method shouldStep takes nothing returns boolean if(.list.size > 0 or (.on)) then return true else call MAIN_TIME.removeObj(this) call UNLinkedList.destroy(.list) call ImageTrail.destroy(this) return false endif return false endmethod method step takes nothing returns nothing local integer i = 0 local real x = GetUnitX(.caster) local real y = GetUnitY(.caster) local real distsquared = ( (x-.x)*(x-.x) ) + ( (y-.y)*(y-.y) ) local unit trailimage local integer fade local UNNode node local UNNode temp if(.on and .constraint==0) then if distsquared != 0 then set trailimage = CreateUnit(.p,'hIMG',x,y,GetUnitFacing(.caster)) call .list.add(trailimage) call SetUnitTimeScale(trailimage,.01) call SetUnitX(trailimage,x) call SetUnitY(trailimage,y) call SetUnitFlyHeight(trailimage,GetUnitFlyHeight(.caster),100000) call SetUnitUserData(trailimage,255) endif call DestroyEffect(.blur) set .blur = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl", .caster, "origin") endif if .list.size > 0 then set node = .list.head loop set trailimage = node.obj set fade = GetUnitUserData(trailimage) if (fade<=1) then call .list.removeNode(node) call RemoveUnit(trailimage) else call SetUnitVertexColor(trailimage,200,200,200,fade) //call BJDebugMsg("Fading, interval = "+I2S(td.fadeinterval)) call SetUnitUserData(trailimage,fade-.fadeinterval) endif exitwhen node == .list.end or .list.size == 0 set node = node.next endloop endif if .duration >= .maxtime then set .on = false endif set .x = x set .y = y if (.constraint == .spawntime) then set .constraint = 0 else set .constraint = .constraint + 1 endif set trailimage = null endmethod endstruct endlibrary And this is an example spell for the system (it creates a trail of images following the unit. I could change it to take the unit type as a parameter, but for my purposes hardcoding was sufficient). The basic idea of the system is to have only one timer running at a time, that loops through a list of objects implementing a "timedObj" interface, and stepping the ones that declare they should be stepped. It's worked pretty well for me so far, but it can get strained by many copies of some of my more extravagant spells running at once. Anyway, thoughts, critiques, scathing criticisms? |
| 04-28-2009, 11:28 AM | #2 | |
Quote:
|
| 05-13-2009, 01:56 AM | #3 |
Interesting, I didn't know interface calls were slow. Do all calls to methods on interfaced objects use .evaluate? Or only when the object is in interface scope? ie JASS:struct SpecificObject implements InterfaceObject method step takes nothing returns nothing call BJDebugMsg("foobar") endmethod endstruct interface InterfaceObject method step takes nothing returns nothing endinterface function SlowOrNot takes nothing returns nothing local SpecificObject specObj = SpecificObject.allocate() local InterfaceObject interObj = specObj //1 call interObj.step() //2 call specObj.step() endfunction do 1 and 2 both use .evaluate? Or only 1? In any case, it wouldn't be too difficult to have a timer for each struct type if that'd eliminate the .evaluate slowness. Then again, PeriodicStructs seem to do pretty much the same thing, so maybe I'll just adapt those. |
| 05-13-2009, 01:33 PM | #4 |
Only interface calls use .evaluate (well, that and method calls if the method is declared below the point from where you're calling it), otherwise struct methods are functions and are called as such. |
