System unitgroup

Sgqvur

FullOfUltimateTruthsAndEt ernalPrinciples, i.e shi
Reaction score
62
unitgroup is an attempt to replace the [ljass]native group[/ljass] type with a non native one (a struct type)
which is much easier to work with and is hopefully faster =).

Reasons that make the native group type "hard" to work with is that it doesn't know it's size (how many units there are),
requires enumeration callbacks (ForGroup) or FirstOfGroup enumerating for every operation performed on the group including
counting the number of units which are not really a good practice and doesn't have random access.

Requirements:
jasshelper compiler by Vexorian​
AutoIndex by grim001 or​
UnitIndexer by Nestharus​


The API is presented in C/C++ / C# / cJass / Galaxy Script function notation for brevity in pairs in which the
top one is the [ljass]group[/ljass] function name (if present) and the bottom is the [ljass]unitgroup[/ljass] alternative:

[ljass]group CreateGroup()[/ljass]
[ljass]static unitgroup new()[/ljass]

-- no native or bj available but possible --
[ljass]static unitgroup copy(unitgroup which_unitgroup)[/ljass]

[ljass]void GroupClear(group g)[/ljass]
[ljass]void clear()[/ljass]

[ljass]void DestroyGroup(group g)[/ljass]
[ljass]void delete()[/ljass]

[ljass]void GroupRemoveUnit(group g, unit u)[/ljass]
[ljass]void remove_unit(unit u)[/ljass]

[ljass]void GroupAddUnit(group g, unit u)[/ljass]
[ljass]void push_unit(unit u)[/ljass]

[ljass]bool IsUnitInGroup(unit u, group g)[/ljass]
[ljass]bool has_unit(unit u)[/ljass]

[ljass]unit GroupPickRandomUnit(group g)[/ljass]
[ljass]unit random_unit()[/ljass]

[ljass]void GroupEnumUnitsOfType(group g, string unitname, boolexpr filter)[/ljass]
[ljass]void enum_units_by_id(int id, int player, int max_count)[/ljass]

[ljass]void GroupEnumUnitsOfTypeCounted(group g, string unitname, boolexpr filter, int max_count)[/ljass]
[ljass]void enum_units_by_id(int id, int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsOfPlayer(group g, player p, boolexpr filter)[/ljass]
[ljass]void enum_units_of_player(int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsInRect(group g, rect r, boolexpr filter)[/ljass]
[ljass]void enum_units_in_rect(rect r, int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsInRectCounted(group g, rect r, boolexpr filter, int max_count)[/ljass]
[ljass]void enum_units_in_rect(rect r, int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsInRange(group g, real x, real y, real radius, boolexpr filter)[/ljass]
[ljass]void enum_units_in_range(real x, real y, real radius, int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsInRangeCounted(group g, real x, real y, real radius, boolexpr filter, int max_count)[/ljass]
[ljass]void enum_units_in_range(real x, real y, real radius, int p, int max_count)[/ljass]

[ljass]void GroupEnumUnitsSelected(group g, player p, boolexpr filter)[/ljass]
[ljass]void enum_units_selected_by_player(int p, int max_count)[/ljass]

[ljass]bool GroupImmediateOrderById(group g, int order)[/ljass]
[ljass]void immediate_order(int order)[/ljass]

[ljass]bool GroupPointOrderById(group g, int order, real x, real y)[/ljass]
[ljass]void point_order(int order, real x, real y)[/ljass]

[ljass]bool GroupTargetOrderById(group g, int order, widget target)[/ljass]
[ljass]void target_order(int order, widget target)[/ljass]

-- no native or bj available but possible --
[ljass]unit nearest_unit_from_point(real x, real y)[/ljass]

-- no native or bj available but possible --
[ljass]unit nearest_unit_from_unit(unit u)[/ljass]

[ljass]void ForGroup(group g, code callback)[/ljass]
JASS:
// equivalent in Zinc notation:

integer i = 0;
unit u;
for (0 <= i < |unitgroup_instance|.size)
{
    u = |unitgroup_instance|<i>;
    // do stuff with u
}
</i>


[ljass]unit FirstOfGroup(group g)[/ljass]
[ljass]|unitgroup_instance|[0][/ljass]

[ljass]int CountUnitsInGroup(group g)[/ljass]
[ljass]|unitgroup_instance|.size[/ljass]


JASS:
library unitgroup uses optional AutoIndex, optional UnitIndexer

    globals
    
    private constant boolean USE_ARRAYS_FOR_STORAGE = true
   
    // if the above constant is true then only 
    // UNITGROUP_MAX_INSTANCES unitgroups are allowed
    // each with maximum size of UNITGROUP_MAX_SIZE units
    private constant integer UNITGROUP_MAX_INSTANCES = 320
    private constant integer UNITGROUP_MAX_SIZE      = 1024
    private unit array UGC[UNITGROUP_MAX_INSTANCES][UNITGROUP_MAX_SIZE]
    // Note: UNITGROUP_MAX_INSTANCES * UNITGROUP_MAX_SIZE &lt;= 409 550 must be true
    //       else a compile time error will pop

    // else if the above constant is false then
    // 8190 unitgroups are allowed each with
    // an &quot;unlimited&quot; (limited by ram only) amount of units
    private hashtable HT

    // in terms of speed the 2d array (UGC) should be faster
    // so set the constant to true if you prefer speed
    // else set it to false if you want to be safe than sorry =)

    endglobals




    globals

    // used in some enum&lt;*&gt; methods
    constant integer ANY_PLAYER   = -1

    // could be used in all enum&lt;*&gt; methods
    constant integer NO_MAX_COUNT = 0

    private unit    array Units
    private integer       Units_count = 0

    endglobals

    private struct indexer extends array

    static if LIBRARY_AutoIndex then

        static method index takes unit u returns nothing
            set Units[Units_count] = u
            set Units_count = Units_count + 1
        endmethod

        static method deindex takes unit u returns nothing
            local integer i          = 0
            local integer unit_index = 0

            loop
                exitwhen  i &gt;= Units_count
                if Units<i> == u then
                    set unit_index = i
                    exitwhen true
                endif

                set i = i + 1
            endloop

            set Units[unit_index] = Units[Units_count - 1]
            set Units_count = Units_count - 1
        endmethod

        static method onInit takes nothing returns nothing
            call OnUnitIndexed(indexer.index)
            call OnUnitDeindexed(indexer.deindex)
        endmethod

    elseif LIBRARY_UnitIndexer then

        method index takes nothing returns nothing
            set Units[Units_count] = unit
            set Units_count = Units_count + 1
        endmethod

        method deindex takes nothing returns nothing
            local integer i          = 0
            local integer unit_index = 0

            loop
                exitwhen  i &gt;= Units_count
                if Units<i> == unit then
                    set unit_index = i
                    exitwhen true
                endif

                set i = i + 1
            endloop

            set Units[unit_index] = Units[Units_count - 1]
            set Units_count = Units_count - 1

        endmethod
        
        implement optional UnitIndexStruct

    else
        compiletimeexeption e = &quot;You need a unit indexing library =)&quot;
        throw e
    endif

    endstruct


           
    struct unitgroup

        // the number of units in the unitgroup
        readonly integer size


        //! textmacro unitgroup_assert_size takes METHOD_NAME, RETURN_VALUE
                debug if size &lt;= 0 then
                debug     call BJDebugMsg(&quot;unitgroup error: method $METHOD_NAME$: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: unitgroup is empty&quot;)
                debug     return $RETURN_VALUE$
                debug endif
        //! endtextmacro

        method operator[] takes integer i returns unit
            debug if  i &lt; 0 or i &gt;= size  then
            debug     call BJDebugMsg(&quot;unitgroup error: method operator[]: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: array index out of bounds&quot;)
            debug     return null
            debug endif

            static if USE_ARRAYS_FOR_STORAGE then
                return UGC[this]<i>
            else
                return LoadUnitHandle(HT, this, i)
            endif
        endmethod

        method operator[]= takes integer i, unit u returns nothing
            debug if  i &lt; 0 or i &gt;= size  then
            debug     call BJDebugMsg(&quot;unitgroup error: method operator[]=: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: array index out of bounds&quot;)
            debug     return
            debug endif

            static if USE_ARRAYS_FOR_STORAGE then
                set UGC[this]<i> = u
            else
                call SaveUnitHandle(HT, this, i, u)
            endif
        endmethod

        method has_unit takes unit which_unit returns boolean
            local integer i     = 0
            local unit    u     = null
            local boolean found = false

            //! runtextmacro unitgroup_assert_size(&quot;has_unit&quot;, &quot;false&quot;)

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif

                if u == which_unit then
                    set found = true
                    exitwhen true
                endif

                set i = i + 1
            endloop
        
            set u = null
            return found
        endmethod

        method remove_unit takes unit u returns nothing
            local integer i          = 0
            local integer unit_index = -1

            //! runtextmacro unitgroup_assert_size(&quot;remove_unit&quot;, &quot;&quot;)

            if u == null then
                debug call BJDebugMsg(&quot;unitgroup error: method remove_unit: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: attempt to remove null unit&quot;)
                return
            endif
            
            loop
                exitwhen i &gt;= size
                static if USE_ARRAYS_FOR_STORAGE then
                    if u == UGC[this]<i> then
                        set unit_index = i
                        exitwhen true
                    endif
                else
                    if u == LoadUnitHandle(HT, this, i) then
                        set unit_index = i
                        exitwhen true
                    endif
                endif
    
                set i = i + 1
            endloop
                        
            // if the unit was found
            if unit_index != -1 then
                static if USE_ARRAYS_FOR_STORAGE then
                    set UGC[this][unit_index] = UGC[this][size -1]
                    set size = size - 1
                else                        
                    call SaveUnitHandle(HT, this, unit_index, LoadUnitHandle(HT, this, size - 1))
                    set size = size -1
                endif
            endif
        endmethod

        method push_unit takes unit u returns nothing
            static if USE_ARRAYS_FOR_STORAGE then
                set UGC[this][size] = u
            else
                call SaveUnitHandle(HT, this, size, u)
            endif
            set size = size + 1
        endmethod

        method random_unit takes nothing returns unit

            //! runtextmacro unitgroup_assert_size(&quot;random_unit&quot;, &quot;null&quot;)

            static if USE_ARRAYS_FOR_STORAGE then
                return UGC[this][GetRandomInt(0, size -1)]
            else
                return LoadUnitHandle(HT, this, GetRandomInt(0, size - 1))
            endif
        endmethod


        method enum_units_of_player takes integer p, integer max_count returns nothing
            local integer i = 0
            local integer n = 0
            local player  P = null
            local unit    u = null                       

            if p == ANY_PLAYER then
                debug call BJDebugMsg(&quot;unitgroup error: method enum_units_of_player: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: expected a specific player not ANY_PLAYER&quot;)
                return
            endif

            set P = Player(p)
    
            loop
                exitwhen i &gt;= Units_count or (n &gt;= max_count and max_count != NO_MAX_COUNT)
                               
                set u = Units<i>
                if P == GetOwningPlayer(u) then

                    // push the unit
                    static if USE_ARRAYS_FOR_STORAGE then
                        set UGC[this][size] = u
                    else
                        call SaveUnitHandle(HT, this, size, u)
                    endif
                    set size = size + 1 
                endif

                set i = i + 1
                set n = n + 1
            endloop

            set u = null
        endmethod

        method enum_units_by_id takes integer id, integer p, integer max_count returns nothing
            local integer i = 0
            local integer n = 0
            local player  P = null
            local unit    u = null

            if id == 0 then
                debug call BJDebugMsg(&quot;unitgroup error: method enum_units_by_id: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: invalid unit id&quot;)
                return
            endif

            if p != ANY_PLAYER then
                set P = Player(p)
            endif
            
            loop
                exitwhen i &gt;= Units_count or (n &gt;= max_count and max_count != NO_MAX_COUNT)
                               
                set u = Units<i>
                if (p == ANY_PLAYER or P == GetOwningPlayer(u)) and id == GetUnitTypeId(u) then

                    static if USE_ARRAYS_FOR_STORAGE then
                        set UGC[this][size] = u
                    else
                        call SaveUnitHandle(HT, this, size, u)
                    endif
                    set size = size + 1 
                endif

                set i = i + 1
                set n = n + 1
            endloop

            set u = null
        endmethod

        method enum_units_in_rect takes rect r, integer p, integer max_count returns nothing
            local integer i    = 0
            local integer n    = 0
            local player  P    = null
            local unit    u    = null
            local real    ux   = 0
            local real    uy   = 0
            local real    minx = 0
            local real    maxx = 0
            local real    miny = 0
            local real    maxy = 0

            if r == null then
                debug call BJDebugMsg(&quot;unitgroup error: method enum_units_in_rect: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: expected a non null rect&quot;)
                return
            endif

            if p != ANY_PLAYER then
                set P = Player(p)
            endif

            set minx = GetRectMinX(r)
            set maxx = GetRectMaxX(r)
            set miny = GetRectMinY(r)
            set maxy = GetRectMaxY(r)
            
            loop
                exitwhen i &gt;= Units_count or (n &gt;= max_count and max_count != NO_MAX_COUNT)
                               
                set u = Units<i>
                set ux = GetUnitX(u)
                set uy = GetUnitY(u)
                if (p == ANY_PLAYER or P == GetOwningPlayer(u)) and minx &lt; ux and ux &lt; maxx and miny &lt; uy and uy &lt; maxy then

                    static if USE_ARRAYS_FOR_STORAGE then
                        set UGC[this][size] = u
                    else
                        call SaveUnitHandle(HT, this, size, u)
                    endif
                    set size = size + 1 
                endif

                set i = i + 1
                set n = n + 1
            endloop

            set u = null
        endmethod

        method enum_units_in_range takes real x, real y, real radius, integer p, integer max_count returns nothing
            local integer i = 0
            local integer n = 0
            local player  P = null
            local unit    u = null

            if radius &lt;= 0 then
                debug call BJDebugMsg(&quot;unitgroup error: method enum_units_in_range: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: invalid radius&quot;)
                return
            endif

            if p != ANY_PLAYER then
                set P = Player(p)
            endif

            loop
                exitwhen i &gt;= Units_count or (n &gt;= max_count and max_count != NO_MAX_COUNT)
                               
                set u = Units<i>
                if (p == ANY_PLAYER or P == GetOwningPlayer(u)) and IsUnitInRangeXY(u, x, y, radius) then

                    static if USE_ARRAYS_FOR_STORAGE then
                        set UGC[this][size] = u
                    else
                        call SaveUnitHandle(HT, this, size, u)
                    endif
                    set size = size + 1 
                endif

                set i = i + 1
                set n = n + 1
            endloop

            set u = null
        endmethod

        method enum_units_selected_by_player takes integer p, integer max_count returns nothing
            local integer i = 0
            local integer n = 0
            local player  P = null
            local unit    u = null

            if p == ANY_PLAYER then
                debug call BJDebugMsg(&quot;unitgroup error: method enum_units_selected_by_player: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: expected a specific player not ANY_PLAYER&quot;)
                return
            endif

            set P = Player(p)

            loop
                exitwhen i &gt;= Units_count or (n &gt;= max_count and max_count != NO_MAX_COUNT)
                               
                set u = Units<i>
                if IsUnitSelected(u, P) then

                    static if USE_ARRAYS_FOR_STORAGE then
                        set UGC[this][size] = u
                    else
                        call SaveUnitHandle(HT, this, size, u)
                    endif
                    set size = size + 1 
                endif

                set i = i + 1
                set n = n + 1
            endloop

            set u = null
        endmethod


        method immediate_order takes integer order returns nothing
            local integer i = 0
            local unit    u = null

            //! runtextmacro unitgroup_assert_size(&quot;immediate_order&quot;, &quot;&quot;)

            if order == 0 then
                debug call BJDebugMsg(&quot;unitgroup error: method immediate_order: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: ivalid order&quot;)
                return
            endif

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif

                call IssueImmediateOrderById(u, order)

                set i = i + 1
            endloop

            set u = null
        endmethod

        method point_order takes integer order, real x, real y returns nothing
            local integer i = 0
            local unit    u = null

            //! runtextmacro unitgroup_assert_size(&quot;point_order&quot;, &quot;&quot;)

            if order == 0 then
                debug call BJDebugMsg(&quot;unitgroup error: method point_order: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: ivalid order&quot;)
                return
            endif

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif

                call IssuePointOrderById(u, order, x, y)

                set i = i + 1
            endloop

            set u = null
        endmethod

        method target_order takes integer order, widget target returns nothing
            local integer i = 0
            local unit    u = null

            //! runtextmacro unitgroup_assert_size(&quot;target_order&quot;, &quot;&quot;)

            if order == 0 then
                debug call BJDebugMsg(&quot;unitgroup error: method target_order: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: ivalid order&quot;)
                return
            elseif target == null then
                debug call BJDebugMsg(&quot;unitgroup error: method target_order: &quot; + &quot;instance id = &quot; + I2S(this) + &quot;: expected a non null target&quot;)
                return
            endif

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif

                call IssueTargetOrderById(u, order, target)

                set i = i + 1
            endloop

            set u = null
        endmethod


        method nearest_unit_from_point takes real x, real y returns unit
            local integer i                 = 0
            local unit    u                 = null
            local real    ux                = 0
            local real    uy                = 0
            local real    curr_min_distance = 9999999
            local unit    nearest_unit      = null
            local real    dx                = 0
            local real    dy                = 0
            local real    distance          = 0

            //! runtextmacro unitgroup_assert_size(&quot;nearest_unit_from_point&quot;, &quot;null&quot;)

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif
                
                set ux = GetUnitX(u)
                set uy = GetUnitY(u)
                set dx = ux - x
                set dy = uy - y
                set distance = SquareRoot(dx * dx + dy * dy)

                if curr_min_distance &gt; distance then
                    set curr_min_distance = distance
                    set nearest_unit = u
                endif

                set i = i + 1
            endloop
                    
            set u = null

            return nearest_unit
        endmethod

        method nearest_unit_from_unit takes unit which_unit returns unit
            local integer i                 = 0
            local unit    u                 = null
            local real    ux                = 0
            local real    uy                = 0
            local real    curr_min_distance = 9999999
            local unit    nearest_unit      = null
            local real    dx                = 0
            local real    dy                = 0
            local real    distance          = 0
            local real    x                 = GetUnitX(which_unit)
            local real    y                 = GetUnitY(which_unit)

            //! runtextmacro unitgroup_assert_size(&quot;nearest_unit_from_unit&quot;, &quot;null&quot;)

            loop
                exitwhen i &gt;= size

                static if USE_ARRAYS_FOR_STORAGE then
                    set u = UGC[this]<i>
                else
                    set u = LoadUnitHandle(HT, this, i)
                endif

                if u != which_unit then
                
                    set ux = GetUnitX(u)
                    set uy = GetUnitY(u)
                    set dx = ux - x
                    set dy = uy - y
                    set distance = SquareRoot(dx * dx + dy * dy)

                    if curr_min_distance &gt; distance then
                        set curr_min_distance = distance
                        set nearest_unit = u
                    endif

                endif

                set i = i + 1
            endloop
                    
            set u = null

            return nearest_unit
        endmethod


        static method new takes nothing returns unitgroup
            local unitgroup ug = allocate()

            debug static if USE_ARRAYS_FOR_STORAGE then
            debug    if integer(ug) &gt; UNITGROUP_MAX_INSTANCES then
            debug        call BJDebugMsg(&quot;unitgroup error: constructor: out of unitgroups&quot;)
            debug        return unitgroup(0)
            debug    endif
            debug  endif

            set ug.size = 0

            return ug
        endmethod

        static method copy takes unitgroup which_unitgroup returns unitgroup
            local unitgroup ug = allocate()
            local integer   i  = 0

            debug static if USE_ARRAYS_FOR_STORAGE then
            debug    if integer(ug) &gt; UNITGROUP_MAX_INSTANCES then
            debug        call BJDebugMsg(&quot;unitgroup error: copy constructor: out of unitgroups&quot;)
            debug        return unitgroup(0)
            debug    endif
            debug  endif

            loop
                exitwhen i &gt;= which_unitgroup.size

                static if USE_ARRAYS_FOR_STORAGE then
                    set UGC[ug]<i> = UGC[which_unitgroup]<i>
                else
                    call SaveUnitHandle(HT, ug, i, LoadUnitHandle(HT, which_unitgroup, i))
                endif

                set i = i + 1
            endloop

            set ug.size = which_unitgroup.size

            return ug
        endmethod

        method clear takes nothing returns nothing
            set size = 0
        endmethod

        method delete takes nothing returns nothing
            call deallocate()
        endmethod

        private static method onInit takes nothing returns nothing
            static if USE_ARRAYS_FOR_STORAGE then
            else
                set HT = InitHashtable()
            endif
        endmethod

    endstruct
    
endlibrary
</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
A good idea, but a linked list group will be much faster.
And someone already benchmarked custom group, it is still slower than native group.

So, there is no point to reinvent the wheel.
 

Laiev

Hey Listen!!
Reaction score
188
[ljass]library unitgroup uses optional AutoIndex, optional UnitIndexer[/ljass]

AutoIndex? Serious? You should support AIDS instead of AutoIndex.

Not everyone code in [ljass]zinc[/ljass], so you should change this:

[ljass]unit random_unit()[/ljass]

Sorry but is just bad.

[ljass].unitRandom() -> unit[/ljass]

And so for the others.
 

Laiev

Hey Listen!!
Reaction score
188
JASS:
/*~*/
        private static method onInit takes nothing returns nothing
            static if USE_ARRAYS_FOR_STORAGE then
            else
                set HT = InitHashtable()
            endif
        endmethod


Why not:

JASS:
/*~*/
        private static method onInit takes nothing returns nothing
            static if not USE_ARRAYS_FOR_STORAGE then
                set HT = InitHashtable()
            endif
        endmethod


Or better:

JASS:
/*~*/
        private module M
            private static method onInit takes nothing returns nothing
                static if not USE_ARRAYS_FOR_STORAGE then
                    set HT = InitHashtable()
                endif
            endmethod
        endmodule
        private struct S extends array
            implement M
        endstruct


?

Also you leak your function of nearest unit :)
 

tooltiperror

Super Moderator
Reaction score
231
Idea has been revisited many times. Speed gains are negligible (or nonexistent) and this has been debated ad nauseam.

Graveyarded unless a very strong rebuttal is provided to the points in the GroupTools thread.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top