- Joined
- Nov 3, 2006
- Messages
- 8,102
So essentially, I've been thinking of writing up on the API and functionality of two simple modules I've made to streamline some systems I've also made, then I got to thinking they might be useful to other people. The purpose of these modules is not to bring any sort of new, groundbreaking functionality at all, it's to simplify some things.
First we have LinkedStruct. This library lets you call .alloc(), .collect() and .dealloc() to allocate, deallocate in the next frame, and deallocate immediately.
It has a built in linked list that contains instance members.
Why would I do this, you ask? Well, my idea is as follows: I want to be able to iterate through the list of instances as easily, consistently and safely as possible. Therefore, to do so I can just do this:
This is all nice and all, but what's the .collect() for? Well, .collect() deallocates the instance we collected with a 0.00 timer delay. This means that during the execution of our iteration the underlying linked list will not change, aside from maybe having new members attached at the end of it, in which case they'll just be iterated over as well. In case we destroy some members the list will not act funky in any way, it'll just be circumvented around them.
And now about Looper: Whenever you have a system that requires a single big timer to run it, you can use something like this. Essentially it includes methods to automatically manage whether the timer runs or not. It uses LinkedStruct to determine the number of current instances of the struct, and then modifies the timer timeout according to that. How is it used, though? Well...
However, in order for Looper to work, you need to define a static real variable called looperTimeout and set its value to your preferred timeout. However, for every 10 instances, this number will be increased by the base value. Which means if you set your timer to 0.01 timeout for a knockback system, once there are 50 instances, it'll run at 0.05, and automatically update once the number of instances goes down. You can, of course, do this manually every time you create/destroy instances, this just does that automatically.
LinkedStruct
JASS:
module LinkedStruct
private static integer maxInstances = 8190
private static integer count = 0
private static thistype head = 0
private static thistype last = 0
private static boolean array active
private static boolean array used
private static integer array next
private static integer array prev
private static integer lastid = 0
private static thistype array recycled
private static integer recycledCount = 0
private static timer deallocTimer = CreateTimer()
private static boolean deallocRunning = false
private static thistype array deallocInstances
private static integer deallocCount = 0
public method getMaxInstances takes nothing returns integer
return maxInstances
endmethod
public method setMaxInstances takes integer max returns nothing
set maxInstances = max
endmethod
method exists takes nothing returns boolean
return used[this]
endmethod
static method getCount takes nothing returns integer
return count
endmethod
static method getHead takes nothing returns thistype
return head
endmethod
static method getFirst takes nothing returns thistype
return head.getNext()
endmethod
static method getLast takes nothing returns thistype
return last
endmethod
method getNext takes nothing returns thistype
local thistype inst = this
loop
set inst = next[inst]
if active[inst] then
return inst
else
if inst == head then
return inst
endif
endif
endloop
return thistype(0)
endmethod
method getPrev takes nothing returns thistype
local thistype inst = this
loop
// Looping until we find the first list member
// that is labeled active, or we'll hit the head member
// and promptly just return it
set inst = prev[inst]
if active[inst] then
return inst
else
if inst == head then
return inst
endif
endif
endloop
return thistype(0)
endmethod
private static method getFreeIndex takes nothing returns integer
if count == maxInstances then
return 0
endif
if recycledCount == 0 then
set lastid = lastid + 1
set used[lastid] = true
return thistype(lastid)
else
set recycledCount = recycledCount - 1
set used[recycled[recycledCount]] = true
return recycled[recycledCount]
endif
endmethod
public static method alloc takes nothing returns thistype
// First, let's secure an unique used index
local integer new = getFreeIndex()
// Then let's check if we're actually given one
if new != 0 then
// Increase the counter
set count = count + 1
// Let's add the new element to the right (next) side
// of the last element in the linked list
set next[last] = new
set prev[new ] = last
// Since this is a doubly linked list, we'll have the
// new last element point to the head now, and vice versa
set last = new
set next[last] = head
set prev[head] = last
// When an instance member is set to active, it won't be
// skipped over during the list iteration
set active[new] = true
endif
return thistype(new)
endmethod
public method dealloc takes nothing returns nothing
// We are only deallocating the instances that are labeled as
// used in order to
if used[this] then
set active[this] = false
set used[this] = false
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
if this == last then
set last = prev[this]
endif
set prev[this] = 0
set next[this] = 0
set recycled[recycledCount] = this
set recycledCount = recycledCount + 1
endif
endmethod
private static method timedDealloc takes nothing returns nothing
local integer i = 0
loop
exitwhen i >= deallocCount
call deallocInstances[i].dealloc()
set i = i + 1
endloop
set deallocCount = 0
set deallocRunning = false
endmethod
public method collect takes nothing returns nothing
// It is no longer active, but the index is not used until it's deallocated
if active[this] then
set active[this] = false
// Add it to the dealloc list
set deallocInstances[deallocCount] = this
set deallocCount = deallocCount + 1
// If the deallocation system is not already running
if not deallocRunning then
call TimerStart(deallocTimer, 0, false, function thistype.timedDealloc)
set deallocRunning = true
endif
// Reduce count
set count = count - 1
endif
endmethod
First we have LinkedStruct. This library lets you call .alloc(), .collect() and .dealloc() to allocate, deallocate in the next frame, and deallocate immediately.
It has a built in linked list that contains instance members.
Why would I do this, you ask? Well, my idea is as follows: I want to be able to iterate through the list of instances as easily, consistently and safely as possible. Therefore, to do so I can just do this:
JASS:
thistype instance = thistype.getHead()
loop
set instance = instance.getNext()
exitwhen instance == 0
//Do whatever with instance
endloop
This is all nice and all, but what's the .collect() for? Well, .collect() deallocates the instance we collected with a 0.00 timer delay. This means that during the execution of our iteration the underlying linked list will not change, aside from maybe having new members attached at the end of it, in which case they'll just be iterated over as well. In case we destroy some members the list will not act funky in any way, it'll just be circumvented around them.
Looper
JASS:
module Looper
implement optional LinkedStruct
private static timer looperTimer = CreateTimer()
private static boolean looperRunning = false
private static real looperDefault = -666
public static method startLooper takes nothing returns nothing
if not looperRunning and getCount() > 0 then
call TimerStart(looperTimer, looperTimeout, true, function thistype.looper)
set looperRunning = true
endif
endmethod
public static method stopLooper takes nothing returns nothing
if looperRunning then
call PauseTimer(looperTimer)
set looperRunning = false
endif
endmethod
public static method setLooperTimeout takes real timeout returns nothing
set looperTimeout = timeout
if getCount() > 0 then
if looperRunning then
call stopLooper()
call startLooper()
else
call startLooper()
endif
endif
endmethod
public static method looperAutoSetup takes nothing returns nothing
local integer c = getCount()
if looperDefault == -666 then
set looperDefault = looperTimeout
elseif c == 0 and not looperRunning then
call stopLooper()
elseif c < 10 and looperTimeout != looperDefault then
call setLooperTimeout(looperDefault)
elseif (c / 10) * 10 == c then
call setLooperTimeout(looperDefault * (1+(c/10)))
elseif c == 0 then
call stopLooper()
endif
call startLooper()
endmethod
endmodule
And now about Looper: Whenever you have a system that requires a single big timer to run it, you can use something like this. Essentially it includes methods to automatically manage whether the timer runs or not. It uses LinkedStruct to determine the number of current instances of the struct, and then modifies the timer timeout according to that. How is it used, though? Well...
JASS:
public static method create takes nothing returns thistype
local thistype instance = thistype.alloc()
//stuff
call looperAutoSetup()
return instance
endmethod
JASS:
public method onDestroy takes nothing returns nothing
call looperAutoSetup()
endmethod
However, in order for Looper to work, you need to define a static real variable called looperTimeout and set its value to your preferred timeout. However, for every 10 instances, this number will be increased by the base value. Which means if you set your timer to 0.01 timeout for a knockback system, once there are 50 instances, it'll run at 0.05, and automatically update once the number of instances goes down. You can, of course, do this manually every time you create/destroy instances, this just does that automatically.