- Joined
- Mar 18, 2012
- Messages
- 1,716
I want to share a small algorithm I'm using successfully since some time.
It concerns the
Trackable objects can only be created, not destroyed. Also they are if used in a more advanced way player based objects.
Normally only one custom UI can be opened per player. Hence if that UI
has a MUI design ( Inventory, Skill tree, ... ) and the UI is 100% similar
for all units ( buttons on the same position ) only one set of trackables
has to be used for all units of a player.
I'm using a tile size of 128 like it is defined in the blizzard.j,
but actually you can lower it which can be useful if you use 4x4 or 8x8 trackables.
Also a TableArray[MAX_PLAYERS] would be enough instead of a seperate hashtable.
Example: Using this algorithm, my custom inventory requires ~50 trackables for 1 unit of player 1.
And also needs only ~50 trackables for 1000 units of player 1.
Without this design it would be 50000 trackables.
Edit: Here is the onHover and onClick interface for better understanding.
It concerns the
trackable
handle, more specifically a wrapper type to trackables named Track
.Trackable objects can only be created, not destroyed. Also they are if used in a more advanced way player based objects.
Normally only one custom UI can be opened per player. Hence if that UI
has a MUI design ( Inventory, Skill tree, ... ) and the UI is 100% similar
for all units ( buttons on the same position ) only one set of trackables
has to be used for all units of a player.
I'm using a tile size of 128 like it is defined in the blizzard.j,
but actually you can lower it which can be useful if you use 4x4 or 8x8 trackables.
Also a TableArray[MAX_PLAYERS] would be enough instead of a seperate hashtable.
Example: Using this algorithm, my custom inventory requires ~50 trackables for 1 unit of player 1.
And also needs only ~50 trackables for 1000 units of player 1.
Without this design it would be 50000 trackables.
JASS:
//**
//* Track search & save:
//* ====================
//*
//* Trackables are poorly supported in Warcraft III. In fact we can't
//* remove handles of type "trackable" once they are no longer used.
//* Therefore I came up with an algorithm to search out of "all ever created Tracks in UIWindows"
//* these which match 100% to the one we wish to create and use that Track instance instead.
//*
//* library TileDefinition creates unique integers per tile in your map.
//* Tracks are saved in stacks on the tile id in an hashtable.
//* With an 64x64 track model the loop will exit after (1 - 3) * players loops.
//* For bigger model it will be 1 * players, for smallers the loopup time will increase.
//*
//* Due to an hashtable lookup during both trackable events ( hover & click ),
//* a Track instance can evaluate any UIWindow onHover/onClick stub method without
//* running into code collision with other UIs which use the same Track instance.
//* Credits to IcemanBo:
//* ====================
static if not LIBRARY_TileDefinition then
private static integer WorldTiles_X = 0
private static integer WorldTiles_Y = 0
private static method setVars takes nothing returns nothing
set WorldTiles_X = R2I(WorldBounds.maxX - WorldBounds.minX) / 128 + 1
set WorldTiles_Y = R2I(WorldBounds.maxY - WorldBounds.minY) / 128 + 1
endmethod
private static method GetTileId takes real x, real y returns integer
local integer xI = R2I(x - WorldBounds.minX + 64) / 128
local integer yI = R2I(y - WorldBounds.minY + 64) / 128
if ((xI < 0) or (xI >= .WorldTiles_X) or (yI < 0) or (yI >= .WorldTiles_Y)) then
return -1
endif
return (yI * .WorldTiles_X + xI)
endmethod
endif
private method saveTrack takes Track track returns Track
local integer id = GetTileId(track.x, track.y)
local integer size = LoadInteger(TRACKER, id, 0) + 1
call SaveInteger(TRACKER, id, size, track)
call SaveInteger(TRACKER, id, -size, userId)
call SaveInteger(TRACKER, id, 0, size)
return track
endmethod
private method searchTrack takes string model, real x, real y, real z, real face returns Track
local integer id = GetTileId(x, y)
local integer size = LoadInteger(TRACKER, id, 0)
local Track t
loop
exitwhen 0 == size
if (LoadInteger(TRACKER, id, -size) == userId) then
set t = LoadInteger(TRACKER, id, size)
if (t.x == x) and (t.y == y) and (t.z == z) and (t.facing == face) and (t.model == model) then
return t
endif
endif
set size = size - 1
endloop
return saveTrack(Track.createForPlayer(model, x, y, z, face, user))
endmethod
Edit: Here is the onHover and onClick interface for better understanding.
JASS:
//* Stub methods:
//* =============
public stub method onHover takes UIScreen whichScreen, UIButton hovered returns nothing
endmethod
public stub method onClick takes UIScreen whichScreen, UIButton clicked returns nothing
endmethod
//* UIButton API:
//* =============
//* Adds an UIButton to your custom UI. The function uses a search & save algorithm for Tracks
//* ( read above ) to optimize the trackable handle count in your map.
method addDestButton takes string model, real x, real y, real z, real face, integer objectId, real scale returns UIButton
local integer typeId = getType()
local integer size = getButtonCount()
local Track track = searchTrack(model, originX + x, originY + y, z, face)
debug call ThrowError(invalid, "UIWindow", "addButton", "invalid", this, "Can't add further UIButtons, once method addPages is called!")
set cells[size] = UIButton.createDest(typeId, user, objectId, track, scale*screen.scale, false)
set cells[TABLE_SIZE]= size + 1
set pageSize = size + 1
call SaveInteger(TABLE, screen, -track, this)
call SaveInteger(TABLE, screen, track, size)
return cells[size]
endmethod
method addImageButton takes string model, real x, real y, real z, real face, string file, real scale returns UIButton
local integer typeId = getType()
local integer size = getButtonCount()
local Track track = searchTrack(model, originX + x, originY + y, z, face)
debug call ThrowError(invalid, "UIWindow", "addButton", "invalid", this, "Can't add further UIButtons, once method addPages is called!")
set cells[size] = UIButton.createImg(typeId, user, file, track, scale*screen.scale, false)
set cells[TABLE_SIZE]= size + 1
set pageSize = size + 1
call SaveInteger(TABLE, screen, -track, this)
call SaveInteger(TABLE, screen, track, size)
return cells[size]
endmethod
//* Trackable events:
//* =================
private static method onAnyHover takes nothing returns boolean
local UIScreen UI = GetPlayerUI(Track.tracker)
local thistype this = LoadInteger(TABLE, UI, -Track.instance)
local integer index = LoadInteger(TABLE, UI, Track.instance) + (pageSize*(page - 1))
local UIButton hovered = cells[index]
if (this != 0) and (hovered.enabled) then
call onHover(UI, hovered)
endif
return false
endmethod
private static method onAnyClick takes nothing returns boolean
local UIScreen UI = GetPlayerUI(Track.tracker)
local thistype this = LoadInteger(TABLE, UI, -Track.instance)
local integer index = LoadInteger(TABLE, UI, Track.instance) + (pageSize*(page - 1))
local UIButton clicked = cells[index]
if (0 != this) and (clicked.enabled) then
call onClick(UI, clicked)
set lastClick = UI_GetElapsedTime()
endif
return false
endmethod
private static method init takes nothing returns nothing
call Track.registerAnyClick(function thistype.onAnyClick)
call Track.registerAnyHover(function thistype.onAnyHover)
static if not LIBRARY_TileDefinition then
call thistype.setVars()
endif
endmethod
implement UIInit