- Joined
- May 9, 2014
- Messages
- 1,820
GroupT
A Generic Group library that allows dynamic generation of structs that serve as containers for any kind of data.
Code
Documentation
Version History
Features to be added (by request)
Features to be added (planned)
JASS:
library GenericGroup requires /*
*/ Table, /*
*
*/ AllocationAndLinks /*
*
*/
public struct HASH extends array
static constant hashtable HASH = InitHashtable()
method destroy takes nothing returns nothing
if LoadInteger(HASH, 0, this) != 0 then
return
endif
call SaveInteger(HASH, 0, this, LoadInteger(HASH, 0, 0))
call SaveInteger(HASH, 0, 0, this)
endmethod
static method new takes nothing returns integer
local integer this = LoadInteger(HASH, 0, 0)
if LoadInteger(HASH, 0, this) == 0 then
set this = this + 1
call SaveInteger(HASH, 0, 0, this)
else
call SaveInteger(HASH, 0, 0, LoadInteger(HASH, 0, this))
call RemoveSavedInteger(HASH, 0, this)
endif
return this
endmethod
endstruct
//! runtextmacro DLinkedListT("list", "")
//! textmacro GroupT takes T, K, I, T1, T2, T3, NaN, NaNT2
module $T$GroupM
private static method onInit takes nothing returns nothing
set Value = GenericGroup_HASH.new()
set Count = GenericGroup_HASH.new()
set TriggerStorage = GenericGroup_HASH.new()
set ConditionStorage = GenericGroup_HASH.new()
set IsBeingRemoved = GenericGroup_HASH.new()
debug set AllowDebug = GenericGroup_HASH.new()
endmethod
endmodule
$K$ struct $T$Group extends array
implement AllocH
implement DoubleLink_list
private static thistype array current
private static constant integer SPACE = $I$
private static integer Value = 0
private static integer Count = 0
private static integer TriggerStorage = 0
private static integer ConditionStorage = 0
private static integer IsBeingRemoved = 0
debug private static integer AllowDebug = 0
private integer Storage
/* ================== //
* Variable operators //
* ================== // */
private method setCount takes integer newValue returns nothing
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, Count, this, newValue)
endmethod
method getCount takes nothing returns integer
return LoadInteger(GenericGroup_HASH.GenericGroup_HASH, Count, this)
endmethod
method operator $T1$= takes $T3$ newR returns nothing
if not (newR != $NaN$) then
call RemoveSaved$NaNT2$(GenericGroup_HASH.GenericGroup_HASH, Value, this)
else
call Save$T2$(GenericGroup_HASH.GenericGroup_HASH, Value, this, newR)
endif
endmethod
method operator $T1$ takes nothing returns $T3$
return Load$T2$(GenericGroup_HASH.GenericGroup_HASH, Value, this)
endmethod
/* ================== //
* Safety checker //
* ================== // */
private method isHead takes nothing returns boolean
return this.list_head != 0
endmethod
/* ================= //
* Instance getter //
* ================= // */
private method updatePositions takes integer instruction, integer value returns nothing
local integer updateCount = this.getCount()/thistype.SPACE
local thistype that = 0
if instruction == 1 then
// Insertion call from add().
set that = value
if ModuloInteger(this.getCount(), thistype.SPACE) == 0 then
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount, that)
endif
elseif instruction == 2 then
// Insertion call from add_pos().
// Start-off index.
set value = value/thistype.SPACE
if LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount) == 0 then
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount, this.list_prev)
set updateCount = updateCount - 1
endif
loop
exitwhen updateCount <= value
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount, thistype(LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount) ).list_prev)
set updateCount = updateCount - 1
endloop
elseif instruction == 3 then
// Delete call from node_pop()
// Start-off index.
set value = value/thistype.SPACE
if updateCount < (this.getCount() + 1)/thistype.SPACE then
call RemoveSavedInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount + 1)
endif
loop
exitwhen updateCount <= value
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount, thistype(LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, updateCount) ).list_next)
set updateCount = updateCount - 1
endloop
endif
endmethod
static if DEBUG_MODE then
method operator debug_mode takes nothing returns boolean
if isHead() then
return LoadBoolean(GenericGroup_HASH.GenericGroup_HASH, AllowDebug, this)
endif
return false
endmethod
method operator debug_mode= takes boolean b returns nothing
if isHead() then
if b then
call SaveBoolean(GenericGroup_HASH.GenericGroup_HASH, AllowDebug, this, b)
else
call RemoveSavedBoolean(GenericGroup_HASH.GenericGroup_HASH, AllowDebug, this)
endif
endif
endmethod
endif
method operator [] takes integer pos returns thistype
local integer mod_pos = 0
local integer temp_pos = 0
local thistype that = 0
if not isHead() or pos > this.getCount() or pos < 0 then
debug if not isHead() then
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: op_getindex[] ] [Instance " + I2S(this) + "] Invalid pointer exception!")
debug elseif pos > this.getCount() then
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: op_getindex[] ] [Instance " + I2S(this) + "] Out of bounds exception! (Positive)")
debug elseif pos < 0 then
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: op_getindex[] ] [Instance " + I2S(this) + "] Out of bounds exception! (Negative)")
debug endif
return 0
endif
set mod_pos = ModuloInteger(pos, thistype.SPACE)
set temp_pos = this.getCount()/thistype.SPACE*thistype.SPACE
if pos > temp_pos then
if mod_pos > thistype.SPACE/2 then
set that = this.list_prev
loop
exitwhen pos >= this.getCount()
set that = that.list_prev
set pos = pos + 1
endloop
else
set that = LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, temp_pos/thistype.SPACE)
loop
exitwhen ModuloInteger(pos, thistype.SPACE) <= 0
set that = that.list_next
set pos = pos - 1
endloop
endif
else
if mod_pos > thistype.SPACE/2 then
set that = LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, (pos/thistype.SPACE) + 1)
loop
exitwhen ModuloInteger(pos, thistype.SPACE) <= 0
set that = that.list_prev
set pos = pos + 1
endloop
else
set that = LoadInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, pos/thistype.SPACE)
loop
exitwhen ModuloInteger(pos, thistype.SPACE) <= 0
set that = that.list_next
set pos = pos - 1
endloop
endif
endif
return that
endmethod
method add takes $T3$ val returns nothing
local thistype that = 0
if isHead() then
set that = allocate()
set that.$T1$ = val
call that.list_insert(this)
call this.setCount(this.getCount() + 1)
call updatePositions(1, that)
debug else
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: add] [Instance " + I2S(this) + "] Invalid pointer exception!")
endif
endmethod
method add_pos takes $T3$ val, integer pos returns nothing
local thistype that = 0
if isHead() then
if pos > this.getCount() then
call add(val)
return
elseif pos < 0 then
return
endif
set pos = IMaxBJ(1, pos)
set that = allocate()
set that.$T1$ = val
call that.list_insert(this[pos])
call this.setCount(this.getCount() + 1)
call updatePositions(2, pos)
debug else
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: add_pos] [Instance " + I2S(this) + "] Invalid pointer exception!")
endif
endmethod
method preset takes integer amount returns nothing
loop
exitwhen amount <= 0 or not isHead()
call add($NaN$)
set amount = amount - 1
endloop
debug if not isHead() then
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: preset] [Instance " + I2S(this) + "] Invalid pointer exception!")
debug endif
endmethod
method clearData takes nothing returns nothing
local thistype last = this.list_prev
if isHead() then
loop
exitwhen last == this
set last.$T1$ = $NaN$
set last = last.list_prev
endloop
set this.$T1$ = $NaN$
debug else
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: clearData] [Instance " + I2S(this) + "] Invalid pointer exception!")
endif
endmethod
method node_pop takes integer pos returns nothing
local thistype that = 0
local thistype array callbackList
local boolean b = LoadBoolean(GenericGroup_HASH.GenericGroup_HASH, IsBeingRemoved, this)
if isHead() and not b then
if pos <= 0 or pos > this.getCount() then
return
endif
set that = this[pos]
set callbackList[1] = current[1]
set callbackList[2] = current[2]
set current[1] = that
set current[2] = this
debug if this.debug_mode then
debug call BJDebugMsg("$T$Group [" + I2S(this) + "]: Position of node: " + I2S(pos))
debug endif
call SaveBoolean(GenericGroup_HASH.GenericGroup_HASH, IsBeingRemoved, this, true)
call TriggerEvaluate(LoadTriggerHandle(GenericGroup_HASH.GenericGroup_HASH, TriggerStorage, this))
call RemoveSavedBoolean(GenericGroup_HASH.GenericGroup_HASH, IsBeingRemoved, this)
set current[2] = callbackList[2]
set current[1] = callbackList[1]
set that.$T1$ = $NaN$
call that.list_pop()
call that.deallocate()
call this.setCount(this.getCount() - 1)
call this.updatePositions(3, pos - 1)
endif
endmethod
static method getRemovedNode takes nothing returns thistype
return current[1]
endmethod
static method getOnRemoveHeadNode takes nothing returns thistype
return current[2]
endmethod
private static method exec_clear takes nothing returns nothing
local thistype this = current[0]
local integer i = 1
loop
exitwhen i > 60 or this.getCount() <= 0
call this.node_pop(this.getCount())
set i = i + 1
endloop
if this.getCount() > 0 then
call ForForce(bj_FORCE_PLAYER[0], function thistype.exec_clear)
endif
endmethod
method setOnDestroy takes code destfunc returns nothing
local trigger t = null
if isHead() then
set t = LoadTriggerHandle(GenericGroup_HASH.GenericGroup_HASH, TriggerStorage, this)
if t == null then
set t = CreateTrigger()
call SaveTriggerHandle(GenericGroup_HASH.GenericGroup_HASH, TriggerStorage, this, t)
endif
if LoadTriggerConditionHandle(GenericGroup_HASH.GenericGroup_HASH, ConditionStorage, this) != null then
call TriggerRemoveCondition(t, LoadTriggerConditionHandle(GenericGroup_HASH.GenericGroup_HASH, ConditionStorage, this))
endif
call SaveTriggerConditionHandle(GenericGroup_HASH.GenericGroup_HASH, ConditionStorage, this, TriggerAddCondition(t, Condition(destfunc)))
set t = null
endif
endmethod
method clear takes nothing returns boolean
if isHead() and this.getCount() > 0 then
set current[0] = this
call ForForce(bj_FORCE_PLAYER[0], function thistype.exec_clear)
endif
return isHead()
endmethod
method destroy takes nothing returns nothing
if not clear() then
debug call BJDebugMsg("|cffffcc00$T$Group|r: [Method: destroy] [Instance: " + I2S(this) + "] invalid object exception!")
return
endif
call list_pop()
set list_head = 0
call RemoveSavedInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, 0)
call GenericGroup_HASH(Storage).destroy()
set Storage = 0
call DestroyTrigger(LoadTriggerHandle(GenericGroup_HASH.GenericGroup_HASH, TriggerStorage, this))
call RemoveSavedHandle(GenericGroup_HASH.GenericGroup_HASH, TriggerStorage, this)
call RemoveSavedHandle(GenericGroup_HASH.GenericGroup_HASH, ConditionStorage, this)
set $T1$ = $NaN$
call deallocate()
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set Storage = GenericGroup_HASH.new()
call this.list_insert(this)
set this.list_head = 1
call SaveInteger(GenericGroup_HASH.GenericGroup_HASH, this.Storage, 0, this)
return this
endmethod
implement $T$GroupM
endstruct
//! endtextmacro
//! runtextmacro GroupT("Real", "", "8", "real", "Real", "real", "0.", "Real")
endlibrary
JASS:
//! novjass
/**
*
* --------------------------------
* Generic Groups or GroupT
* --------------------------------
*
* Welcome to the documentation portion of the library aforementioned (above).
* This will serve to fulfill the following objectives:
* - To inform the reader/s about the functionality of the library.
* - To inform the reader/s about the nature of the library.
* - To inform the reader/s about the purpose of the library.
*
* For ease of reference, the Table of Contents is placed below with the appropriate line number
*
* -------------------------------
* | Table of Contents |
* -------------------------------
* | |
* | Functionalities: 29 |
* | Textmacros : 32 |
* | Methods : 79 |
* | Nature : 207 |
* | Purpose : 221 |
* | Libraries used : 229 |
* | Anticipated Q : 262 |
* -------------------------------
*
* Functionalities:
* - This library provides the following functionalities:
*
* - Textmacros:
* - GroupT(T, K, I, T1, T2, T3, NaN, NaNT2)
* - This textmacro generates a struct which contains a certain type as declared in
* the following parameters:
* T - Refers to the prefix of the struct:
* - in this case, if T is Real, then the resulting struct is RealGroup
*
* K - Refers to the encapsulation of the struct:
* - if K is private, then the struct becomes private, only being
* accessible to the enclosing library or scope.
*
* I - Refers to the constant space between instances.
* - "I" takes an integer parameter. Aside from that, the constant serves
* to reduce the number of operations to O(I) and O(n/I) respectively.
* (This refers to a read operation (worst-case) and insertion and
* deletion (worst-case)).
*
* T1 - Refers to the operator of an individual instance.
* - For example, if T is Int, and T1 is real, we can simply write
* Int(2).real. However, this direct reference to instances is frowned
* upon.
*
* T2 - Refers to the equivalent native of the desired type.
* - From the previous example, say T2 is Boolean.
* - Thus, the following output would become something like this:
* set Int(2).real = false
*
* T3 - Refers to the type and return type of the instance.
* - This is the <T> part in Group<T>, which asserts strong type-safety.
* - This returns the appropriate data type from the request.
* - Now, if T = Real, T1 = real, T2 = Real, and T3 = real, the result
* would be:
* Real(3).real = real r (sets the real member of instance 3 to
* (the appropriate literal r.)
*
* NaN - Refers to the actual null-type of the instance.
* - The null-type of the instance is equivalent to the null-value of the
* type of the instance.
* - For example, 0 is the usual null-type of integer (in JASS),
* - false is the null-type in boolean expressions (again in JASS).
*
* NaNT2 - Refers to the equivalent removal native of the desired type.
* - There are five natives specifically for removing the contents of a
* hashtable, namely RemoveSavedInteger, ..Real, ..Boolean, ..Handle,
* ..String.
*
*
* - Generic Methods:
*
* -------------
* NOTE:
* -------------
* * Functions annotated with debug only exist in DEBUG_MODE.
*
------------------------------------------------------------------------------------------------
* private method setCount(int newValue)
* - An internal method primarily used within head instances.
* - Giving this functionality to member instances would complicate things...
* operation-wise.
*
* method getCount() returns int
* - Returns the number of instances in the list.
* - Will only work with head instances due to the nature of setCount.
*
* method operator $T1$() returns $T3$
* - Explained in the Textmacro section (see $T1$ & $T3$).
* - The getter operator of the instance.
*
* method operator $T1$= ($T3$ newValue)
* - Explained in the Textmacro section (see $T1$ & $T3$).
* - The setter operator of the instance.
*
* private method isHead() returns boolean
* - A safety method that checks if the instance is a head instance.
*
* private method updatePositions(int instruction, int value)
* - This handles the repositioning of instances upon insertion or deletion.
* - This only affects head nodes.
*
* - For each instruction {1, 2, 3}, there corresponds a task.
* - For instruction 1, it simply creates a new pointer to the last instance
* if the modulo of the number of instances and the amount of space is equal
* to 0.
* - That means if the amount of space is 8 ($I$ = 8), then a new pointer
* is created every 8 insertions.
*
* - For instruction 2, it adjusts the pointers such that the minimum amount
* of operations is achieved. This best averts the worst-case scenario, in
* a sense. It is called on insertion at a certain position.
*
* - For instruction 3, it adjusts the pointer such that the minimum amount
* of operations is achieved. It is called on deletion at a certain
* position.
*
* debug method operator debug_mode= (boolean b)
* - In debug mode, this prints out the details on the processes involved.
* - This only happens if debug_mode is set to true (through the setter method)
*
* debug method operator debug_mode() returns boolean
* - In debug_mode, this returns the flag for producing additional debug
* messages.
* - This is the getter method for the flag.
*
* static method getRemovedNode() returns $T$Group
* - This returns the instance currently being removed.
* - Data persists only on notification of destruction.
* - Recursion-safe.
*
* static method getOnRemoveHeadNode() returns $T$Group
* - This returns the head node of the instance currently being removed.
* - For example, let head = 1, and node = 2
* When 2 is removed, via node_pop(), getOnRemoveHeadNode will point to 1.
* - Data persists only on notification of destruction.
* - Recursion-safe.
*
* private static method exec_clear()
* - This is the loop-method for the instance method clear().
* - This clears out the list of a head instance, by calling node_pop().
* - For best results, the last node is removed first to minimize operation count.
* - In another sense, this does the dirty work while clear sends out the
* instructions.
* - This clears out the pointer reference through updateInstructions through
* node_pop.
*
* method clear() returns boolean
* - This internally calls exec_clear() without having to suffer an op-limit
* crash.
* - It checks if the instance is a head instance before calling exec_clear().
* - The result of the condition is what is returned.
* - Thus, it is safe to use in scripting.
*
* method setOnDestroy(code func)
* - This attaches a handler function to be run on the popping of a certain
* instance from a head instance.
* - This checks if the instance is a head instance before proceeding.
*
* method destroy()
* - This destroys the head instance, and ignores member instances.
* - This internally calls clear() beforehand, and recycles the pointer list
* to be used later on.
* - In DEBUG_MODE, this prints an error message.
*
* static method create() returns $T$Group
* - This creates a head instance, which you can do a lot of things with.
*
* method add($T3$ newValue)
* - This adds an entry with the requested value.
* - This internally calls updatePosition with the first instruction, and value equal
* to the allocated node instance (which holds said entry)
* - This checks if the requesting instance is a head instance and proceeds if so.
* - In DEBUG_MODE, this prints an error message.
*
* method add_pos($T3$ newValue, integer pos)
* - This adds an entry at a desired index, pushing the instance found therein.
* - This internally calls updatePosition with the second instruction, and value
* equal to the index (pos).
* - If it exceeds the number of instances within the list, it calls add instead.
* - In DEBUG_MODE, this prints an error message.
*
* method preset(integer amount)
* - This adds a certain number of fields equal to the requested amount.
* - This internally checks if the instance is a head instance and breaks immediately
* if so.
* - In DEBUG_MODE, this prints an error message.
*
* - Modules:
* - Module $T$GroupM
* - This is basically the initializer of the necessary variables.
* - This is best left untouched by the end-user.
*
* Aside from those methods, the library also features a struct RealGroup, generated from
* the GroupT textmacro.
*
* ------------------------------------------------------------------------------------------------
*
* Nature:
*
* The structs generated from the textmacro GroupT are, by nature, OOP-oriented.
* The group structs, as they are to be called from this point, make it easier for one to
* dynamically attach data at any amount.
* This can be very useful in attaching data to missiles such as the attributes of the
* missile and the user-data fed to the missile, attaching data to units or timers, and even
* just creating effect groups, destructable groups and item groups altogether, although
* with the need for abstraction for the latter.
* In short, this allows a lot of things that need to be clumped up into groups to be
* done easier, and without too much of a hassle, and with needed type-checking.
*
* ------------------------------------------------------------------------------------------------
*
* Purpose:
*
* This library aims to simplify a lot of things, as well as provide vJASS with de-facto
* type safety in the matters of groups. This also acts as one crucial part in the making
* of a standard vJASS library.
*
* ------------------------------------------------------------------------------------------------
*
* Libraries used:
*
* This libraries uses the following libraries, with a succinct explanation as to why these
* libraries are required.
*
* */
Library:
Table - Bribe
/**
* - Table is a library that has a lot of practical uses, aside from simplifying the life of
* a coder in matters of hashtables. That being said, Table is not really required as a
* whole in this library, but why it is required anyway is because of the next library
* requirement.
*
* URL: https://www.hiveworkshop.com/threads/snippet-new-table.188084/
* */
AllocationAndLinks - MyPad
/**
* - AllocationAndLinks is just one part of 4 libraries, which are -> AllocT, LinkedListT
* and AllocLink. As AllocationAndLinks requires AllocLink, so does AllocLink require
* AllocT and LinkedListT, which optionally requires Table (the library above).
* The hashtable allocation method could not be left ignored, thus leading to the
* requirement of the library above, which, for hashtable allocation, requires Table.
*
* URL: https://www.hiveworkshop.com/threads/allocation-and-links.293621/#post-3159130
*
* ------------------------------------------------------------------------------------------------
*
* Anticipated Questions:
*
* ----------------------
*
* Why have another hashtable HASH if you could just use Table instances?
* - I preferred to be on the safe side of things, so I had to use another hashtable
* so that I don't end up using too many Table instances.
*
* Why is the HASH struct public? Does it mean we can use it?
* - For the intents and purposes of the coder, the HASH is better treated as a
* private struct in terms of usage, but that doesn't mean it isn't public.
* The HASH struct is public because of the following reason, the ability for
* generated struct groups to access the HASH struct, which would be rendered
* impossible to access from the outside if the HASH struct is private.
*
**/
//! endnovjass
- v.1.0 - Release!
- None.
- Search method.