System vJUI - vJASS Unique Indexing & Object Attacher

SerraAvenger

Cuz I can
Reaction score
234
This is for indexing rawids and strings with unique indicies between 1 and 8191, abusing hashtables and structs. It is quite simple indeed, but very useful.

It actually _is_ able to get indicies for handleid's, too. Not for handles though, so when the handleid get's recycled you might have a bunch of trouble.

Note that a string might get the same index like a handleid (or rawid) and vv.

If you require more than 8k for something special (like a random numbers indexer), _please_ instanciate the textmacro again (although you aren't supposed to ;D)
JASS:
//...
  call GetUniqueIndex( 'I005' ) // -> integer between 1 and 8k, doesn't change
  call GetUniqueStringIndex( "Hey, babe!" ) // -> integer between 1 and 8k, maybe the same.
//...

JASS:
library Hashich

// This is for 8000 indicies per field only. If you need more, please instanciate the texmacro again.

//! textmacro HASHICH takes NAME, VAL, TYPE
globals
  hashtable Hashich$NAME$_saver = InitHashtable()
  integer Hashich$NAME$_count = 1
endglobals

function GetUnique$NAME$Index takes $TYPE$ val returns integer
    local integer value = $VAL$
    local integer id = LoadInteger( Hashich$NAME$_saver, value, 0 )
    if id == 0 then
        set id = Hashich$NAME$_count
        set Hashich$NAME$_count = Hashich$NAME$_count + 1
        call SaveInteger( Hashich$NAME$_saver, value, 0, id )
    endif
    return id
endfunction

//! endtextmacro

//! runtextmacro HASHICH( "",       "val",                  "integer" )
//! runtextmacro HASHICH( "String", "StringHash( val )",    "string" )
//! runtextmacro HASHICH( "Handle", "GetHandleId( val )",   "handle" )

endlibrary


Especially useful with the ObjectAttachers that ease the syntax and do nasty stuff for you.

The Object attacher uses textmacros to create simple attachers for rawids and strings (no handles).
JASS:
//...
// Outside of any library/scope:
//! runtextmacro AddObjectValue( "Cost", "integer", "0" )
//! runtextmacro AddStringValue( "PlayerByName", "player", "null" )

//...
    call SetObjectCost( 'I005', 200 )
    call BJDebugMsg( "The current cost are " + I2S( Cost[ 'I005' ] ) + " Gold." ) 
    set Cost[ 'I005' ] = 50000
    call BJDebugMsg( "The current cost are " + I2S( GetObjectCost( 'I005' ) ) + " Gold." ) // Prices grew a bit.
//...
    local integer i = 0
    loop
         exitwhen i == 12
         call SetStringPlayerByName( GetPlayerName( Player( i ) ), Player( i ) )
         set i = i + 1
    endloop
    if PlayerByName[ "SerraAvenger" ] != null then
        call BJDebugMsg( "SerraAvenger is in the game! He occupies slot #" + I2S( GetPlayerId( GetStringPlayerByName( "SerraAvenger" ) ) ) )
    endif

JASS:
///// VER 02 03 - ObjectAttacher

//! textmacro AddObjectValue takes NAME, TYPE, DEFAULT
library_once $NAME$Attacher needs Hashich
globals
    private boolean array isUsed
    private $TYPE$ array Val
    private $TYPE$ Default = $DEFAULT$ 
endglobals

public function GetObjectIndex takes integer id returns integer
    return GetUniqueIndex( id )
endfunction

function SetDefault$NAME$ takes $TYPE$ value returns nothing
    set Default = value
endfunction

function GetDefault$NAME$ takes nothing returns $TYPE$
    return Default
endfunction

function SetObject$NAME$ takes integer id, $TYPE$ value returns nothing
    local integer index = GetUniqueIndex( id )
    if value == Default then
        set isUsed[ index ] = false
    else 
        set isUsed[ index ] = true
        set Val[ index ] = value
    endif    
endfunction


function GetObject$NAME$ takes integer id returns $TYPE$
    local integer index = GetObjectIndex( id )
    local $TYPE$ result = Default
    if isUsed[ index ] then
        set result = Val[ index ]
    endif
    return result
endfunction

struct $NAME$
    static method operator [] takes integer id returns $TYPE$
        return GetObject$NAME$( id )
    endmethod
    static method operator []= takes integer id, $TYPE$ value returns nothing
        call SetObject$NAME$( id, value )
    endmethod
endstruct
endlibrary
//! endtextmacro


//! textmacro AddStringValue takes NAME, TYPE, DEFAULT
library_once $NAME$Attacher needs Hashich
globals
    private boolean array isUsed
    private $TYPE$ array Val
    private $TYPE$ Default = $DEFAULT$ 
endglobals


public function GetStringIndex takes string id returns integer
    return GetUniqueStringIndex( id )
endfunction

function SetDefault$NAME$ takes $TYPE$ value returns nothing
    set Default = value
endfunction

function GetDefault$NAME$ takes nothing returns $TYPE$
    return Default
endfunction

function SetString$NAME$ takes string id, $TYPE$ value returns nothing
    local integer index = GetStringIndex( id )
    if value == Default then
        set isUsed[ index ] = false
    else 
        set isUsed[ index ] = true
        set Val[ index ] = value
    endif    
endfunction


function GetString$NAME$ takes string id returns $TYPE$
    local integer index = GetStringIndex( id )
    local $TYPE$ result = Default
    if isUsed[ index ] then
        set result = Val[ index ]
    endif
    return result
endfunction
struct $NAME$
    static method operator [] takes string id returns $TYPE$
        return GetString$NAME$( id )
    endmethod
    static method operator []= takes string id, $TYPE$ value returns nothing
        call SetString$NAME$( id, value )
    endmethod
endstruct
endlibrary
//! endtextmacro


Best wishes, Serra
 

SerraAvenger

Cuz I can
Reaction score
234
RawCode hashing is linear hashing. This is constant time. And I'ld think it is at least as fast as RawCode hashing, considering I don't do any division or multiplication, while he does both. Actually, I could even get rid of the structs since they will never be destroyed anyway...

RawCode hashing doesn't do strings natively (ofc you can always write a GetStringHash using function, but why bother?)
This has a better name >_>

EDIT:
Removed struct since it was unnecessary anyway.
 

SerraAvenger

Cuz I can
Reaction score
234
Hashish, like the thing that we smoke?

Yes, it's exotic.

I had a library named "Hash" in the battleships crossfire map, which used "hashtable" as a struct name. So when the new patch came out, I had to get rid of that library... It can still be found somewhere in here though, so if you want to see a cool example of doublehashing...

Anyway, I just removed its content and added an 'ich' to the library name (in change). Since I was at say's home at that time, it was incredibly funny* and I left the name.

Now most of my libraries are in desperate need of hashich.

Anyway, back to topic.

*In german, it's haschisch, in english hashish, and ich means I in english, so any number of puns are involved (Hash I?, Hashish, etc etc )
 

Jesus4Lyf

Good Idea™
Reaction score
397
Useless? If you're using a hashtable, you might as well store the struct inside it, directly attached to the integer you're attaching to. No point adding an extra array read and function calls.
JASS:
//! runtextmacro HASHICH( "String", "StringHash( val )",    "string" )
//! runtextmacro HASHICH( "Handle", "GetHandleId( val )",   "handle" )

These only increase coupling, not cohesion. What's wrong with only providing integer? Was the API too easy? Actually, I suppose I understand the value in storing them in a different hashtable on a technical level, but I'm not sure that the indices would actually conflict.
JASS:
globals
  hashtable Hashich$NAME$_saver = InitHashtable()
  integer Hashich$NAME$_count = 1
endglobals

-->
JASS:
globals
  private hashtable Hashich$NAME$_saver = InitHashtable()
  private integer Hashich$NAME$_count = 1
endglobals

I'm confused as to what the other two chunks of code do.

>If you're using a hashtable, you might as well store the struct inside it
Real issue. Using this feels like over-engineering to me.
 

SerraAvenger

Cuz I can
Reaction score
234
Useless? If you're using a hashtable, you might as well store the struct inside it, directly attached to the integer you're attaching to. No point adding an extra array read and function calls.

I take this is obsolete now?

JASS:
//! runtextmacro HASHICH( "String", "StringHash( val )",    "string" )
//! runtextmacro HASHICH( "Handle", "GetHandleId( val )",   "handle" )

These only increase coupling, not cohesion. What's wrong with only providing integer? Was the API too easy? Actually, I suppose I understand the value in storing them in a different hashtable on a technical level, but I'm not sure that the indices would actually conflict.

Well, on a practical level, you're right. The indicies shouldn't conflict for strings and rawids.
Perhaps I'll just use 2 globals instead of 6.
JASS:
library Hashich

// This is for 8000 indicies per field only. If you need more, please instanciate the texmacro again.
globals
  private hashtable Saver = InitHashtable()
  private integer Count = 1
endglobals

//! textmacro HASHICH takes NAME, VAL, TYPE
function GetUnique$NAME$Index takes $TYPE$ val returns integer
    local integer value = $VAL$
    local integer id = LoadInteger( Saver, value, 0 )
    if id == 0 then
        set id = Count
        set Count = Count + 1
        call SaveInteger( Saver, value, 0, id )
    endif
    return id
endfunction
//! endtextmacro

//! runtextmacro HASHICH( "",       "val",                  "integer" )
//! runtextmacro HASHICH( "String", "StringHash( val )",    "string" )
//! runtextmacro HASHICH( "Handle", "GetHandleId( val )",   "handle" )

endlibrary


JASS:
globals
  hashtable Hashich$NAME$_saver = InitHashtable()
  integer Hashich$NAME$_count = 1
endglobals

-->
JASS:
globals
  private hashtable Hashich$NAME$_saver = InitHashtable()
  private integer Hashich$NAME$_count = 1
endglobals
If you're allready doing that private, you can also remove the Hashish$NAME$_. Note that one might try to instanciate the library outside of a library, which will then cause syntax errors.

I'm confused as to what the other two chunks of code do.

They use the unique indexing to store stuff to arrays taking the string / rawid as index.

>If you're using a hashtable, you might as well store the struct inside it
Real issue. Using this feels like over-engineering to me.

Then please show me how to save multiple structs to the same index _without_ using keys or anything like that. Also, this is supposed to be faster since an array lookup is faster than a hashtable lookup, and you can store the index and don't have to lookup / write to the hashtable everytime.

EDIT: Btw, thanks for your interest

EDIT2: Oh and this has another advantage, you may easily use this for Save/Load systems, given you index everything at the very beginning of your map (before indexing anything different).
That way indicies would stay the same everytime the map loads. That means it could be of use to my EasySave system...
 

quraji

zap
Reaction score
144
Then please show me how to save multiple structs to the same index _without_ using keys or anything like that.

To be honest I just slightly skimmed this page, and I'm not sure if this is what you want, but if I needed to save multiple structs to one index:
JASS:

struct datastruct
    //stuff
endstruct

struct storestruct
    datastruct a
    datastruct b
endstruct

/*
call SaveInteger(Hashtable, key, key2, integer(storagestruct))
*/


Or even have datastruct members in datastruct.
 

SerraAvenger

Cuz I can
Reaction score
234
To be honest I just slightly skimmed this page, and I'm not sure if this is what you want, but if I needed to save multiple structs to one index:
JASS:
struct datastruct
    //stuff
endstruct

struct storestruct
    datastruct a
    datastruct b
endstruct

/*
call SaveInteger(Hashtable, key, key2, integer(storagestruct))
*/


Or even have datastruct members in datastruct.

Now have fun retrieving the stuff.


JASS:
// Exp system
local storagestruct hpeaStore = storagestruct.create()
local datastruct hpeaMinXp = datastruct.create( 10 ) // create sets the value variable
local datastruct hpeaMaxXp = datastruct.create( 20 ) 
set hpeaStore.a = hpeaMinXp
set hpeaStore.b = hpeaMaxXp
call SaveInteger(Hashtable, 'hpea', 0, integer(hpeaStore))
//...
local unit dead = GetKilledUnit()
local integer minXp = storagestruct( LoadInteger( Hashtable, GetUnitTypeId( dead ), 0 ) ).a.value
local integer maxXp = storagestruct( LoadInteger( Hashtable, GetUnitTypeId( dead ), 0 ) ).b.value
call AddHeroXp( GetKillingUnit(), GetRandomInt( minXp, maxXp ) )


//// VERSUS

// Exp system
//! runtextmacro AddObjectValue( "MinXp", "integer", "0" )
//! runtextmacro AddObjectValue( "MaxXp", "integer", "0" )
//...
set MinXp[ 'hpea' ] = 10
set MaxXp[ 'hpea' ] = 20
//...
local unit dead = GetKilledUnit()
local integer minXp = MinXp[ GetUnitTypeId( dead ) ]
local integer maxXp = MaxXp[ GetUnitTypeId( dead ) ]
call AddHeroXp( GetKillingUnit(), GetRandomInt( minXp, maxXp ) )


Please make your choice...

EDIT: Actually it's not an option. You would have to create a new hashtable for every system that uses that piece of code, while in the code in post#1 it's 3 total and in #9 just one.
Ofc you can use keys, but it would look like this:
JASS:
globals
    key MAX_XP
    key MIN_XP
endglobals

call SetObjectValue( 'hpea', MIN_XP, 10 )
call SetObjectValue( 'hpea', MAX_XP, 20 )

call GetObjectValue( 'hpea', MIN_XP )
call GetObjectValue( 'hpea', MAX_XP )


Might be interesting to use, but i do love the array syntax.

EDIT2:
This is the GetObjectValue( rawid, key ) stuff.
It does have 2d array syntax and should be fun to use, but it still uses 3 hashtables for a single purpose... And has lots more function calls, since the array lookups have been replaced with hashtable lookups.
On the other hand, it has no requirements.
Has defaults, but can only attach integers which definetly is a disadvantage.
EDIT3:
Updated, it now has Strings too. But for reasons of struct indicies not being strings, it does use the syntax GetObjectValue( key, string ) instead of GetObjectValue( string, key ).
JASS:
///// VER 00 01 - ObjectAttacher

library ObjectValue
globals
    private hashtable Default   = InitHashtable()
    private hashtable UseCheck  = InitHashtable()
    private hashtable Value     = InitHashtable()
endglobals

function SetDefaultObjectValue takes integer key, integer value returns nothing
    call SaveInteger( Default, key, 0, value )
endfunction

function GetDefaultObjectValue takes integer key returns integer 
    return LoadInteger( Default, key, 0 )
endfunction

function HasObjectValue takes integer id, integer key returns boolean
    return LoadBoolean( UseCheck, key, id )
endfunction

private function SaveUsed takes integer id, integer key, boolean inUse returns nothing
    call SaveBoolean( UseCheck, key, id, inUse )
endfunction

function SetObjectValue takes integer id, integer key, integer value returns nothing
    if value == GetDefaultObjectValue( key ) then
        call SaveUsed( id, key, false )
    else 
        call SaveUsed( id, key, true )
        call SaveInteger( Value, key, id, value )
    endif    
endfunction


function GetObjectValue takes integer id, integer key returns integer
    local integer result =  GetDefaultObjectValue( key )
    if HasObjectValue( id, key ) then
        set result = LoadInteger( Value, key, id )
    endif
    return result
endfunction

struct ObjectValue extends array
    method operator [] takes integer key returns integer
        return GetObjectValue( this, key )
    endmethod
    method operator []= takes integer key, integer value returns nothing
        call SetObjectValue( this, key, value )
    endmethod
endstruct
endlibrary

///// VER 00 01 - StringAttacher

library StringValue
globals
    private hashtable Default   = InitHashtable()
    private hashtable UseCheck  = InitHashtable()
    private hashtable Value     = InitHashtable()
endglobals

function SetDefaultStringValue takes integer key, integer value returns nothing
    call SaveInteger( Default, key, 0, value )
endfunction

function GetDefaultStringValue takes integer key returns integer 
    return LoadInteger( Default, key, 0 )
endfunction

function HasStringValue takes integer key, string id returns boolean
    return LoadBoolean( UseCheck, key, StringHash( id ) )
endfunction

private function SaveUsed takes integer key, string id, boolean inUse returns nothing
    call SaveBoolean( UseCheck, key, StringHash( id ), inUse )
endfunction

function SetStringValue takes integer key, string id, integer value returns nothing
    if value == GetDefaultStringValue( key ) then
        call SaveUsed( key, id, false )
    else 
        call SaveUsed( key, id, true )
        call SaveInteger( Value, key, StringHash( id ), value )
    endif    
endfunction


function GetStringValue takes integer key, string id returns integer
    local integer result =  GetDefaultStringValue( key )
    if HasStringValue( key, id ) then
        set result = LoadInteger( Value, key, StringHash( id ) )
    endif
    return result
endfunction

struct StringValue extends array
    method operator [] takes string id returns integer
        return GetStringValue( this, id )
    endmethod
    method operator []= takes string id, integer value returns nothing
        call SetStringValue( this, id, value )
    endmethod
endstruct
endlibrary


It has advantages, but I still like the other way better.
 

Steel

Software Engineer
Reaction score
109
Call it Hash Tables for Dummy.

I agree with J4L. This adds unnecessary function calls and slows down whatever you're storing.

In your example with experience you overcomplicated how to retrieve something from a hash table with your struct to make it look more difficult than it should be. Don't forget that yours calls nearly the same amount of functions, none of your code can be inlined where some of the structs from vJASS can be.
 

SerraAvenger

Cuz I can
Reaction score
234
In your example with experience you overcomplicated how to retrieve something from a hash table with your struct to make it look more difficult than it should be.

I added more lines, but not more complexity. Ofc, you might just as well store integers if you're just going for integers really, but that's just a .value less here and there and doesn't really change complexity.

Don't forget that yours calls nearly the same amount of functions,

I'm thinking I'm calling quite much the same amount, so how is it slowing down everything?
Also, speed ain't everything. Unless you're programming a HAL in JASS, ofc.

I do like the syntax way better than some SaveInteger( ... ) stuff.
Table is fine, but it uses a struct thus is a bit clumsy with being a global
>"Call it Hash Tables for Dummy." - "don't think this is practical enough to approve"
Please decide. Is this something so easy to use that even dummies can use it, or is it not practical enough to approve?

J4L said:
Compare it to Table. Same interface without the runtextmacro?
Textmacros aren't a good end interface, generally. They increase learning curve swiftly.
I agree that textmacros are not the perfect end interface, but their flexibility is great.
Note too that in the post before you I actually posted a system that doesn't use textmacros as UI, but uses keys instead which is a fine syntax too and evades that clumsyness I spoke about.
That one is quite much like table, just that it doesn't use structs but simple counting integers, which also means there can be any number of them. Not that anyone would create 8k tables, but you can never be sure.
EDIT: Table (&my library up there) don't know about types, textmacros allow that.

none of your code can be inlined where some of the structs from vJASS can be.

could you give an example?
I don't see anything that will actually be "inlined". I see a couple of functions that are in fact nonexistant (the struct<->integer cast).
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Table is fine, but it uses a struct thus is a bit clumsy with being a global
Requiring an intialiser doesn't justify a new system, imho.

>>"Call it Hash Tables for Dummy." - "don't think this is practical enough to approve"
>Please decide. Is this something so easy to use that even dummies can use it, or is it not practical enough to approve?
My opinion is definitely the latter.
Graveyarded.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top