• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

hashtable as 2d array benchmark

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
The hashtable native provides an API that resembles the access of a 2d array:

JASS:
native SaveInteger takes hashtable table, integer parentKey, integer childKey, integer value returns nothing
native LoadInteger takes hashtable table, integer parentKey, integer childKey returns integer
...

local integer i1 = LoadInteger(ht, X, Y)
// resembles:
local integer i2 = ht[X][Y]

Suppose that we wanted to use a hashtable as a 2d array with dimensions 1 and 100_000, should we make (X = 1, Y = 100_000), (X = 100_000, Y = 1) or it doesn't matter?

I tried to do an fps benchmark using this (Perl) script (which should be easy to translate to another language):
Code:
use strict;
use warnings;
use feature 'say';

my $OUTPUT_FILE_NAME = 'hashtable-dim-benchmark.j';
my $MAX_ITEMS = 100_000;
my $LOAD_INTEGER_CALLS_COUNT = 20_000;
my $TIMEOUT = '1.0 / 20.0';

# 1 --> SaveInteger(ht, X, 0, X); LoadInteger(ht, X, 0)
# 0 --> SaveInteger(ht, 0, X, X); LoadInteger(ht, 0, X)
# where X is in the range [0, $MAX_ITEMS)
#
my $USE_LEFT_DIM = 1;


my @indices; # an array of indicies that we use to generate the LoadInteger calls
{
    my @all_indices;
    for (my $i = 0; $i < $MAX_ITEMS; $i += 1) {
        push @all_indices, $i;
    }
    @all_indices = shuffle(\@all_indices);


    for (my $i = 0; $i < $LOAD_INTEGER_CALLS_COUNT; $i += 1) {
        push @indices, $all_indices[$i];
    }
}


my $s = '';
{
    $s .= qq|library HashtableDimBenchmark initializer init\n|;
    $s .= qq|\n|;
}

# globals
{
    $s .= qq|globals\n|;
    $s .= qq|    private constant integer MAX_ITEMS = $MAX_ITEMS\n|;

    if ($USE_LEFT_DIM) {
        $s .= qq|    private hashtable ht = InitHashtable() // [MAX_ITEMS][1]\n|;
    }
    else {
        $s .= qq|    private hashtable ht = InitHashtable() // [1][MAX_ITEMS]\n|;
    }

    $s .= qq|    private integer items_count = 0\n|;
    $s .= qq|    private integer index = 0\n|;
    $s .= qq|endglobals\n|;
}

# function ht_init
{
    $s .= qq|\n|;
    $s .= qq|private function ht_init takes nothing returns nothing\n|;
    $s .= qq|    loop\n|;

    if ($USE_LEFT_DIM) {
        $s .= qq|        call SaveInteger(ht, index, 0, index)\n|;
    }
    else {
        $s .= qq|        call SaveInteger(ht, 0, index, index)\n|;
    }

    $s .= qq|        set index = index + 1\n|;
    $s .= qq|        if index >= MAX_ITEMS then\n|;
    $s .= qq|            call BJDebugMsg("ht[0][0] = " + I2S(LoadInteger(ht, 0, 0)))\n|;

    if ($USE_LEFT_DIM) {
        $s .= qq|            call BJDebugMsg("ht[$MAX_ITEMS - 1][0] = " + I2S(LoadInteger(ht, $MAX_ITEMS - 1, 0)))\n|;
    }
    else {
        $s .= qq|            call BJDebugMsg("ht[0][$MAX_ITEMS - 1] = " + I2S(LoadInteger(ht, 0, $MAX_ITEMS - 1)))\n|;
    }

    $s .= qq|            return\n|;
    $s .= qq|        endif\n|;
    $s .= qq|\n|;
    $s .= qq|        // trying not to hit the op-limit\n|;
    $s .= qq|        set items_count = items_count + 1\n|;
    $s .= qq|        if items_count > 1000 then\n|;
    $s .= qq|            set items_count = 0\n|;
    $s .= qq|            call ExecuteFunc(SCOPE_PRIVATE + "ht_init")\n|;
    $s .= qq|            return\n|;
    $s .= qq|        endif\n|;
    $s .= qq|    endloop\n|;
    $s .= qq|endfunction\n|;
}

# function benchmark_loop
{
    $s .= qq|\n|;
    $s .= qq|private function benchmark_loop takes nothing returns nothing\n|;
    $s .= qq|    local integer i\n|;

    for my $index (@indices) {
        if ($USE_LEFT_DIM) {
            $s .= qq|set i = LoadInteger(ht, $index, 0)\n|;
        }
        else {
            $s .= qq|set i = LoadInteger(ht, 0, $index)\n|;
        }
    }

    $s .= qq|endfunction\n|;
}

# init, when the Esc key gets pressed start the fps benchmark
{
    $s .= qq|\n|;
    $s .= qq|private function start_fps_benchmark takes nothing returns nothing\n|;
    $s .= qq|    call ClearTextMessages()\n|;
    $s .= qq|\n|;

    if ($USE_LEFT_DIM) {
        $s .= qq|    call BJDebugMsg("running fps benchmark for hashtable of [$MAX_ITEMS][1] (LoadInteger calls: $LOAD_INTEGER_CALLS_COUNT, timeout: $TIMEOUT)")\n|;
    }
    else {
        $s .= qq|    call BJDebugMsg("running fps benchmark for hashtable of [1][$MAX_ITEMS] (LoadInteger calls: $LOAD_INTEGER_CALLS_COUNT, timeout: $TIMEOUT)")\n|;
    }

    $s .= qq|    call TimerStart(CreateTimer(), $TIMEOUT, true, function benchmark_loop)\n|;
    $s .= qq|endfunction\n|;
    $s .= qq|\n|;
    $s .= qq|private function init takes nothing returns nothing\n|;
    $s .= qq|    local trigger t\n|;
    $s .= qq|\n|;
    $s .= qq|    call ht_init()\n|;
    $s .= qq|\n|;
    $s .= qq|    set t = CreateTrigger()\n|;
    $s .= qq|    call TriggerRegisterPlayerEventEndCinematic(t, Player(0))\n|;
    $s .= qq|    call TriggerAddAction(t, function start_fps_benchmark)\n|;
    $s .= qq|\n|;
    $s .= qq|    call BJDebugMsg("enter the /fps command to show the fps and then")\n|;
    $s .= qq|    call BJDebugMsg("press Esc to start the benchmark")\n|;
    $s .= qq|endfunction\n|;
}

{
    $s .= qq|endlibrary\n|;
}

MAIN: {
    # print $s;
    if (!write_string_to_file($s, $OUTPUT_FILE_NAME)) {
        exit(1);
    }
    exit(0);
}

sub shuffle { my ($array) = @_;
    my @result = @$array; # copy the array

    for (my $i = 0; $i < @result; $i += 1) {
        my $r = get_random_integer(0, $i);
        my $tmp = $result[$i];
        $result[$i] = $result[$r];
        $result[$r] = $tmp;
    }

    @result
}

# Returns a random integer in the range [$x, $y] (both bounds are inclusive).
sub get_random_integer { my ($x, $y) = @_;
    return $x + int(rand() * ($y - $x + 1));
}

sub write_string_to_file { my ($string, $file_name) = @_;
    my $err = !open(my $file, '>', $file_name);
    if ($err) {
        say qq|could not open file "$file_name" for writing"|;
        return ''; # false
    }

    if (! print {$file} $string) {
        say qq|could not write string to file "$file_name"|;
        return '';
    }

    if (!close($file)) {
        say qq|could not close file "$file_name"|;
        return '';
    }

    1; # true
}

It generates some vJass script that does the fps benchmark.

According to my tests it seems that using the hashtable as a [X][Y] (X < Y), is better than [Y][X] (X < Y), i.e
the lower dimension should be passed as the parentKey argument, and the higher dimension should be passed as the childKey parameter to the Save|Load* hashtable natives.
 

Deleted member 219079

D

Deleted member 219079

Whichever causes less collisions is better.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
arrays have way too many restrictions, yet stays as fastest access ever. if you can manage your map wiht them - its good. but real projects would rather use hashtables, sacrificing a bit of CPU for hell lot of readability. maybe vjass would help somebody tho
 
Level 13
Joined
Nov 7, 2014
Messages
571
Aniki how much faster (in microsecond)?
It's an fps benchmark, there are no seconds/microseconds =)?

Is it significant or negligible because I've been doing it wrong all this time?

I would not recommend that you change any of your vJass scripts without running the benchmark for yourself and actually seeing a difference. Maybe the benchmark is wrong, or the speed difference is minuscule/non existent, or maybe I am, you know... lying =).
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
If you dont know how much faster it is, you dont know if it is worth to actually keep in mind.
Not necessarily in (micro)seconds, but at least in percentages relative to the other situation.

"The only downside is that that handleId is too large to fit into an array. If there was a workaround to that.."
Solution: dont use the damned handle ids.

vJASS 2d arrays are simple formulas: a*B_MAX + b = index (array[a] = array[index])
The problem is that you are still bound to 8192 indices... unless you want to make use of the multi array arrays from vJASS, which simply puts the data in more arrays using a "switch case" (just if statements), but that one is even slower than hashtables.
 
I made time test and from my results it didn't really make a difference:

JASS:
function s__S_onInit takes nothing returns nothing
    local hashtable table= InitHashtable()
    local integer parentKey= 0 // or GetHandleId(table)
    local integer childKey= 0 // or GetHandleId(table)
    local integer value= 0 // always same value
    local integer i= 0
      
    call StartPerfCounter()
    loop
        exitwhen i > 10000
        call SaveInteger(table, parentKey, childKey, value)
        set i=i + 1
    endloop
    call StopPerfCounter()
      
endfunction

  • 10000 function calls
  • tested with "0" as parentKey and a big number as parentKey, HandleId
  • tested with "0" as childKey and a big number as childKey, HandleId
  • save value was constant "0"
  • load function was not tested
  • repeated each test setting circa ~10 times to get an average value
Any test settings, no matter if big or low had the more or less same result for me. 125000 - 13500 microseconds for 10000 calls.
It jumped a bit randomly from test to test, probably my pc's conditions or so, but all had pretty same behaviour.
 
Some more tests:

Test Series 1


definitions:

BigNumber = constant HandleId
LowNumber = 0

Operation: 10000 SaveInteger and 10000 LoadInteger from hashtable

case 1

parent = BigNumber ; child = LowNumber
22500 - 44000 Microseconds
FPS: 45 - 50

case 2

parent = LowNumber ; child = BigNumber
21000 - 43000 Microseconds
FPS: 45 - 50

conslusion:

No real different to see. The small difference might be accuracy error.
No FPS difference no note.

test code


JASS:
struct S extends array
  
    static hashtable table = InitHashtable()
    static integer parentKey = 0
    static integer childKey = GetHandleId(table)
    static timer tim = CreateTimer()

    private static method onInit takes nothing returns nothing
        local integer i = 0
      
        //call StartPerfCounter()
      
        loop
            exitwhen i > 10000
            call SaveInteger(table, parentKey, childKey, i)
            call LoadInteger(table, parentKey, childKey)
            set i = i + 1
        endloop
      
       //call StopPerfCounter()
      
       call TimerStart(tim, 0.1, true, function thistype.onInit)
    endmethod
  
endstruct

More tests, based on Aniki's script. Thanks for making it


Test Series 2


definitions:

BigNumber = < A Random Big Number >
LowNumber = 0

Operation: 5000 LoadInteger from hashtable

case 1

parent = BigNumber ; child = LowNumber (true in Aniki's)
7000 - 8000 Microseconds
FPS: 58 - 62

case 2

parent = LowNumber ; child = BigNumber (false in Aniki's)
5600 - 6400 Microseconds
FPS: 58 - 62

extra note

The time benchmarking was pretty strange. When meassured one test by one with big time gaps, the meassured value was almost double as big as the results mentioned above.
But when periodicly meassuring with small intervals (to get average value), the needed time keeps sinking to the half~. Then, if I make a time break and meassure a one-shot again, it again starts at the double of the time.

While running 2 test instances simultaneously, the FPS drop was a lot faster and exreme in case 1 than in case 2.

conclusion:

when Loading same values from hashtable in short time difference the process is faster (?) - what ever this means ;o

(LowNumber, BigNumber) was faster in the test than (BigNumber, LowNumber). Definitly meassurable.
There was no real FPS difference for 1 instance, but an extreme difference for 2 running instances.

test code

(Java Script that creates the vjass test scenario)

Code:
(function() {
'use strict';

// Config
//
var MAX_ITEMS = 100 * 1000; // must be > LOAD_INTEGER_CALLS_COUNT
var LOAD_INTEGER_CALLS_COUNT = 5 * 1000;
var TIMEOUT = '1.0 / 64.0';

// true --> SaveInteger(ht, X, 0, X); LoadInteger(ht, X, 0)
// false --> SaveInteger(ht, 0, X, X); LoadInteger(ht, 0, X)
// where X is in the range [0, MAX_ITEMS)
//
var USE_LEFT_DIM = false;


var indices = []; // an array of indicies that we use to generate the LoadInteger calls
{
    var all_indices = [];
    for (var i = 0; i < MAX_ITEMS; i += 1) {
        all_indices.push(i);
    }
    shuffle(all_indices);

    for (var i = 0; i < LOAD_INTEGER_CALLS_COUNT; i += 1) {
        indices.push(all_indices[i]);
    }
}


var s = '';
{
    s += "library HashtableDimBenchmark initializer init\n";
    s += "\n";
}

// globals
{
    s += "globals\n";
    s += "    private constant integer MAX_ITEMS = " + MAX_ITEMS + "\n";

    if (USE_LEFT_DIM) {
        s += "    private hashtable ht = InitHashtable() // [MAX_ITEMS][1]\n";
    }
    else {
        s += "    private hashtable ht = InitHashtable() // [1][MAX_ITEMS]\n";
    }

    s += "    private integer items_count = 0\n";
    s += "    private integer index = 0\n";
    s += "endglobals\n";
}

// function ht_init
{
    s += "\n";
    s += "private function ht_init takes nothing returns nothing\n";
    s += "    loop\n";

    if (USE_LEFT_DIM) {
        s += "        call SaveInteger(ht, index, 0, index)\n";
    }
    else {
        s += "        call SaveInteger(ht, 0, index, index)\n";
    }

    s += "        set index = index + 1\n";
    s += "        if index >= MAX_ITEMS then\n";
    s += "            call BJDebugMsg(\"ht[0][0] = \" + I2S(LoadInteger(ht, 0, 0)))\n";

    if (USE_LEFT_DIM) {
        s += "            call BJDebugMsg(\"ht[" + MAX_ITEMS + " - 1][0] = \" + I2S(LoadInteger(ht, " + MAX_ITEMS + " - 1, 0)))\n";
    }
    else {
        s += "            call BJDebugMsg(\"ht[0][" + MAX_ITEMS + "- 1] = \" + I2S(LoadInteger(ht, 0, " + MAX_ITEMS + " - 1)))\n";
    }

    s += "            return\n";
    s += "        endif\n";
    s += "\n";
    s += "        // trying not to hit the op-limit\n";
    s += "        set items_count = items_count + 1\n";
    s += "        if items_count > 1000 then\n";
    s += "            set items_count = 0\n";
    s += "            call ExecuteFunc(SCOPE_PRIVATE + \"ht_init\")\n";
    s += "            return\n";
    s += "        endif\n";
    s += "    endloop\n";
    s += "endfunction\n";
}

// function benchmark_loop
{
    s += "\n";
    s += "private function benchmark_loop takes nothing returns nothing\n";
    s += "    local integer i\n";

    for (var i = 0; i < indices.length; i += 1) { var index = indices[i];
        if (USE_LEFT_DIM) {
            s += "set i = LoadInteger(ht, " + index + ", 0)\n";
        }
        else {
            s += "set i = LoadInteger(ht, 0, " + index + ")\n";
        }
    }

    s += "endfunction\n";
}

// init, when the Esc key gets pressed start the fps benchmark
{
    s += "\n";
    s += "private function start_fps_benchmark takes nothing returns nothing\n";
    s += "    call ClearTextMessages()\n";
    s += "\n";

    if (USE_LEFT_DIM) {
        s += "    call BJDebugMsg(\"running fps benchmark for hashtable of [" + MAX_ITEMS + "][1] (LoadInteger calls: " + LOAD_INTEGER_CALLS_COUNT + ", timeout: " + TIMEOUT + ")\")\n";
    }
    else {
        s += "    call BJDebugMsg(\"running fps benchmark for hashtable of [1][" + MAX_ITEMS + "] (LoadInteger calls: " + LOAD_INTEGER_CALLS_COUNT + ", timeout: " + TIMEOUT + ")\")\n";
    }

    s += "    call TimerStart(CreateTimer(), " + TIMEOUT + ", true, function benchmark_loop)\n";
    s += "endfunction\n";
    s += "\n";
    s += "private function init takes nothing returns nothing\n";
    s += "    local trigger t\n";
    s += "\n";
    s += "    call ht_init()\n";
    s += "\n";
    s += "    set t = CreateTrigger()\n";
    s += "    call TriggerRegisterPlayerEventEndCinematic(t, Player(0))\n";
    s += "    call TriggerAddAction(t, function start_fps_benchmark)\n";
    s += "\n";
    s += "    call BJDebugMsg(\"enter the /fps command to show the fps and then\")\n";
    s += "    call BJDebugMsg(\"press Esc to start the benchmark\")\n";
    s += "endfunction\n";
}

{
    s += "endlibrary\n";
}

MAIN: {
    console.log(s);
}

// Shuffles the array in-place.
function shuffle(array) {
    for (var i = 0; i < array.length; i += 1) {
        var r = get_random_integer(0, i);
        var tmp = array[i];
        array[i] = array[r];
        array[r] = tmp;
    }
}

// Returns a random integer in the range [a, b] (both bounds are inclusive).
function get_random_integer(a, b) {
    return a + Math.floor(Math.random() * (b - a + 1));
}

}());



I made more tests with Low/Low and Big/Big, but they did not make a difference to already provided scenarios.
If you found some result confusing, you are not alone. ;D I repeated it pretty often and re-checked all, but always got the same.
Anyone is free to re-test it.^^
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,197
Main problem with hashtable as 2D array is that they do not dynamically resize so as number of mappings increases the performance starts to tank towards O(n) rather than O(1). As such they can only really be recommended as 2D arrays for sparse data, where most indexes are some default value which requires no mapping.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Question: why do we really care? If we optimize code at this level, we might aswell stop coding un Jass in general.

In other words: what's the point? As soon as you create or move a unit, all these optimizations go into diminishing returns.
there could be timer which gather units positions every part of second into arrays and it probably wiser to keep arrays instead of hashtabling them. and there's unit creation as well, sure, which turns all those quircks into nothing when stands nearby.
w/e people do to win another 10 mcs \: D/
 
there could be timer which gather units positions every part of second into arrays
As far as I know, that is pretty much pointless, as GroupEnumUnitsInRange already uses a quad tree approach on the native layer. So the only taxing thing you actually avoid here would be the group handle itself. And I'm not even sure that this matters in comparison to the difference in speed by working on the jass layer.

So even if you hash unit positions into a grid of definable size, chances are, you will still be slower than by just using GroupEnumUnitsInRange in the first place.
A custom-made quad tree makes sense for destructables, where the only enumeration option available relies on having rects and there is no destructablegroup type. For units it's pointless.


There is a reason why nobody coded a quad tree for units yet.
 
Last edited:
Level 19
Joined
Dec 12, 2010
Messages
2,069
As far as I know, that is pretty much pointless, as GroupEnumUnitsInRange already uses a quad tree approach on the native layer. So the only taxing thing you actually avoid here would be the group handle itself. And I'm not even sure that this matters in comparison to the difference in speed by working on the jass layer.

So even if you hash unit positions into a grid of definable size, chances are, you will still be slower than by just using GroupEnumUnitsInRange in the first place.
A custom-made quad tree makes sense for destructables, where the only enumeration option available relies on having rects and there is no destructablegroup type. For units it's pointless.


There is a reason why nobody coded a quad tree for units yet.
groups are counted in pretty tricky way. Firstly it goes through all units (?) as linked list, counting the one who fit condition. Then counts the grid IDs of selected coordinates (x/y). Then it compares current filtered unit grid number to be in the aoe.

this explains why triggered aoes doesn't care about collision sizes as well. from what I see I can tell it asks literally every fitting unit, as well as counting final result for no purpose but GroupEnumUnitsCount. I might be wrong, it's hard to understand how game calculates real address, but loop of grid compares definitely happens without any extra checks except from count limit.

yet anyway, my point was different - I need to know where unit been 5 seconds ago, for instance
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,197
groups are counted in pretty tricky way. Firstly it goes through all units (?) as linked list, counting the one who fit condition. Then counts the grid IDs of selected coordinates (x/y). Then it compares current filtered unit grid number to be in the aoe.

this explains why triggered aoes doesn't care about collision sizes as well. from what I see I can tell it asks literally every fitting unit, as well as counting final result for no purpose but GroupEnumUnitsCount. I might be wrong, it's hard to understand how game calculates real address, but loop of grid compares definitely happens without any extra checks except from count limit.
One can make the filter print out text, I am pretty sure it does not apply the filter to every unit in the map.

What should happen...
  1. Find quad tree leaf closest to requested point.
  2. Traverse upwards until node and all children encompass all units in at least the entire requested area.
  3. Iterate through all units, ignoring any with distance from requested point larger than radius.
  4. Apply filter to iterated unit.
  5. Add iterated units that pass to group.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
One can make the filter print out text, I am pretty sure it does not apply the filter to every unit in the map.

What should happen...
  1. Find quad tree leaf closest to requested point.
  2. Traverse upwards until node and all children encompass all units in at least the entire requested area.
  3. Iterate through all units, ignoring any with distance from requested point larger than radius.
  4. Apply filter to iterated unit.
  5. Add iterated units that pass to group.
my bad, I definitely missed something at filter func wrapper. anyway, filter wrapper located at sub_6F3E01C0

just in case smbd interested in restored code
Code:
int __cdecl sub_6F3C41A0(void *x, signed int *x, signed int *y, void *r, void *a5)
{
  int result; // eax@1
  void *v6; // esi@1
  int v7; // eax@2

  result = sub_6F3BEy0(x);
  v6 = (void *)result;
  if ( result )
  {
    v7 = sub_6F3BD950(a5);
    result = sub_6F3E7C70(g, x, y, r, cond, -1);
  }
  return result;
}

int __thiscall sub_6F3E7C70(void *g, signed int *x, signed int *y, void *r, int c, int -1)
{
  int v6; // esi@1
  int v7; // ecx@1
  int v8; // eax@2

  v6 = (int)g;
  sub_6F3E01C0(g, c, -1, -1, -1);
  v7 = *(_DWORD *)(v6 + 68);
  if ( v7 == -1 )
    v8 = 0;
  else
    v8 = (unsigned __int16)~(1 << v7);
  return sub_6F467570(x, y, r, 0, v8 | 0x14FD0000, 0, 0, sub_6F3E7610, v6);
}

int __thiscall sub_6F3E01C0(void *this, int a2, int a3, int a4, int a5)
{
  void *v5; // esi@1
  int result; // eax@1

  v5 = this;
  sub_6F3DAF00((int)this);
  sub_6F3B55C0((int *)v5 + 14, a2);
  result = a5;
  *((_DWORD *)v5 + 18) = a3;
  *((_DWORD *)v5 + 19) = 0;
  *((_DWORD *)v5 + 16) = a4;
  *((_DWORD *)v5 + 17) = a5;
  return result;
}

int *__thiscall sub_6F3B55C0(int *this, int a2)
{
  int *v2; // ebx@1
  int v3; // esi@2
  int v4; // eax@2
  int v5; // esi@3
  int v6; // ecx@5
  bool v7; // zf@7

  v2 = this;
  if ( a2 && (v3 = sub_6F3B15D0(), v4 = (*(int (__thiscall **)(int))(*(_DWORD *)a2 + 28))(a2), sub_6F471910(v4, v3)) )
    v5 = a2;
  else
    v5 = 0;
  v6 = *v2;
  if ( *v2 != v5 )
  {
    if ( v6 )
    {
      v7 = (*(_DWORD *)(v6 + 4))-- == 1;
      if ( v7 )
        (**(void (***)(void))v6)();
    }
    if ( v5 )
    {
      if ( a2 )
        ++*(_DWORD *)(a2 + 4);
    }
    *v2 = v5;
  }
  return v2;
}

int __thiscall sub_6F3DAF00(int this)
{
  return (*(int (__thiscall **)(int))(*(_DWORD *)(this + 36) + 12))(this + 36);
}

int __fastcall sub_6F467570(signed int *x, signed int *y, void *r, int 0, int a5, int 0, int 0, int (__fastcall *a8)(int, int), int a9)
{
  int result; // eax@2

  if ( 0 )
    result = sub_6F47FB50(x, y, r, a5, 0, 0, a8, a9);
  else
    result = sub_6F47FAF0(x, y, r, a5, 0, 0, a8, a9);
  return result;
}

int __fastcall sub_6F47FAF0(signed int *x, signed int *y, void *r, int a4, int 0, int 0, int (__fastcall *a7)(int, int), int a8)
{
  int result; // eax@4

  if ( 0 && 0 != -1 )
  {
    if ( 0 )
      result = sub_6F47EC80(r, x, y, a4, 0, a7, a8);
    else
      result = sub_6F47EF40(r, x, y, a4, 0, a7, a8);
  }
  else
  {
    result = sub_6F47E9E0(r, x, y, a4, a7, a8);
  }
  return result;
}

int __thiscall sub_6F47E9E0(void *r, signed int *x, signed int *y, int a4, int (__fastcall *a5)(int, int), int a6)
{
  int v6; // edi@1
  unsigned int *v7; // esi@1
  unsigned int *v8; // ebx@1
  unsigned int v9; // eax@1
  int v10; // eax@3
  int v11; // edx@3
  int v12; // esi@3
  bool v13; // zf@3
  int v14; // ebx@7
  int v15; // ST04_4@7
  int v16; // ebx@7
  int v17; // ecx@9
  int v18; // ebx@9
  int result; // eax@13
  int v20; // ebx@14
  int v21; // ebx@15
  int v22; // ecx@16
  int v23; // [sp+Ch] [bp-64h]@1
  unsigned int v24; // [sp+10h] [bp-60h]@1
  unsigned int v25; // [sp+14h] [bp-5Ch]@1
  float v26; // [sp+18h] [bp-58h]@1
  float v27; // [sp+1Ch] [bp-54h]@13
  char v28; // [sp+20h] [bp-50h]@1
  char v29; // [sp+24h] [bp-4Ch]@1
  int v30; // [sp+28h] [bp-48h]@1
  int v31; // [sp+2Ch] [bp-44h]@7
  int v32; // [sp+30h] [bp-40h]@7
  int v33; // [sp+34h] [bp-3Ch]@7
  int v34; // [sp+38h] [bp-38h]@1
  unsigned int v35; // [sp+3Ch] [bp-34h]@1
  int v36; // [sp+40h] [bp-30h]@1
  int v37; // [sp+44h] [bp-2Ch]@1
  unsigned int v38; // [sp+48h] [bp-28h]@1
  unsigned int v39; // [sp+4Ch] [bp-24h]@1
  char v40; // [sp+50h] [bp-20h]@7
  char v41; // [sp+60h] [bp-10h]@7

  v25 = dword_6FAB73D8;
  v6 = dword_6FAB7368;
  v23 = (*(_DWORD *)r - 41943040) & ~((*(_DWORD *)r ^ (*(_DWORD *)r - 50331648)) >> 31);
  sub_6F6EEEF0((signed int *)&v24, y, (_DWORD *)(dword_6FAB7368 + 112));
  v24 = (v24 - 41943040) & ~((signed int)(v24 ^ (v24 - 50331648)) >> 31);
  sub_6F6EEEF0((signed int *)&v26, x, (_DWORD *)(dword_6FAB7368 + 108));
  v34 = (LODWORD(v26) - 41943040) & ~((LODWORD(v26) ^ (LODWORD(v26) - 50331648)) >> 31);
  v35 = v24;
  v7 = sub_6F6EF000((signed int *)&v24, &v34, &v23);
  v8 = sub_6F6EF000((signed int *)&v28, (signed int *)&v35, &v23);
  v26 = COERCE_FLOAT(sub_6F6EEEF0((signed int *)&v29, &v34, &v23));
  v36 = *sub_6F6EEEF0(&v30, (signed int *)&v35, &v23);
  v37 = *(_DWORD *)LODWORD(v26);
  v38 = *v8;
  v39 = *v7;
  v9 = *(_DWORD *)(v6 + 100);
  if ( v9 >= *(_DWORD *)(v6 + 92) )
    sub_6F46A950(v6 + 88, v9 + 1);
  v10 = *(_DWORD *)(v6 + 100);
  v11 = *(_DWORD *)(v6 + 96);
  *(_DWORD *)(v6 + 100) = v10 + 1;
  v12 = *(_DWORD *)(v11 + 4 * v10);
  v13 = *(_DWORD *)(v12 + 32) == 0;
  *(_DWORD *)(v12 + 64) = 1;
  *(_DWORD *)(v12 + 68) = a4;
  if ( !v13 )
  {
    if ( *(_DWORD *)(v12 + 28) )
      sub_6F46AC70(v12, 0, *(_DWORD *)(v12 + 28));
    *(_DWORD *)(v12 + 32) = 0;
  }
  v14 = v25;
  v30 = *(_DWORD *)(sub_6F481420(v25) + 104);
  v31 = v30;
  v32 = v30;
  v33 = v30;
  v15 = sub_6F47D5D0((int)&v40, (int)&v36, (int)&v30);
  sub_6F481420(v14);
  v16 = sub_6F47BA20((int)&v41, v15);
  if ( *(_DWORD *)(v12 + 28) )
    sub_6F46AC70(v12, 0, *(_DWORD *)(v12 + 28));
  *(_DWORD *)(v12 + 36) = *(_DWORD *)v16;
  *(_DWORD *)(v12 + 40) = *(_DWORD *)(v16 + 4);
  v17 = v25;
  *(_DWORD *)(v12 + 44) = *(_DWORD *)(v16 + 8);
  *(_DWORD *)(v12 + 48) = *(_DWORD *)(v16 + 12);
  v18 = sub_6F481420(v17);
  if ( v18 != *(_DWORD *)(v12 + 52) )
  {
    if ( *(_DWORD *)(v12 + 28) )
      sub_6F46AC70(v12, 0, *(_DWORD *)(v12 + 28));
    *(_DWORD *)(v12 + 52) = v18;
  }
  sub_6F47DAD0(v12);
  sub_6F6EEE20((unsigned int *)&v27, &v23, &v23);
  v25 = *(_DWORD *)(v12 + 28);
  result = v25;
  v24 = 0;
  if ( v25 )
  {
    do
    {
      v20 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v12 + 12) + 8 * v24) + 48);
      v30 = *(_DWORD *)(v20 + 120);
      v31 = *(_DWORD *)(v20 + 124);
      sub_6F6EF3B0((signed int *)&v26, (int)&v30, (int)&v34);
      if ( v27 >= (double)v26 )
      {
        v21 = *(_DWORD *)(v20 + 48);
        if ( !*(_DWORD *)(v21 + 32) )
        {
          v22 = *(_DWORD *)(v21 + 84);
          if ( v22 )
          {
            result = a5(v22, a6);
            if ( !result )
              break;
          }
        }
      }
      result = v24++ + 1;
    }
    while ( v24 < v25 );
    *(_DWORD *)(v12 + 64) = 0;
    --*(_DWORD *)(v6 + 100);
  }
  else
  {
    *(_DWORD *)(v12 + 64) = 0;
    --*(_DWORD *)(v6 + 100);
  }
  return result;
}
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,197
just in case smbd interested in restored code
This is about as useful as assembly... Makes perfect sense to a computer but god help you if you are a living creature.

So are you saying that area search native calls become less efficient with more units on the map because they do not use a quad tree but rather a linear search?
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
This is about as useful as assembly... Makes perfect sense to a computer but god help you if you are a living creature.

So are you saying that area search native calls become less efficient with more units on the map because they do not use a quad tree but rather a linear search?
nope, its my bad, blizzard wasn't that stupid
yFr0.png

first 200 units aren't in aoe, and last 200 are in
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,197
first 200 units aren't in aoe, and last 200 are in
Well one can expect that and it is acceptable. More units nearby means more tests and potentially more function calls. As long as units not nearby does not affect performance I say its performance characteristics are acceptable and the functions are perfectly usable as long as used in a sensible way.
 
Status
Not open for further replies.
Top