Page 1 of 1
Cutting GetVisibleUnits searches (optimization)
Posted: 24 Aug 2009, 04:57
by Argh
For projects using a lot of Spring.GetVisibleUnits calls, I've found a major speedup that is easy to achieve.
Simply run one Widget / Gadget to get that value every 10 gameframe / drawframes, and pass that table to everything else in your game globally. If you have some special-needs operations you need to do after that, sort it at the Widget / Gadget level if possible.
This very simple (if boring) optimization of Widgets and Gadgets that need to get the list of visible Units can result in very significant CPU savings, by reducing the total number of searches by large amounts.
Here's a simple example, for Widgets:
First, include a master Widget that provides the info:
Code: Select all
function widget:GetInfo()
return {
name = "Visible Units",
desc = "Speedup for VisibleUnits() callin",
author = "Argh",
date = "August 23, 2009",
license = "Public Domain, or the least-restrictive rights of your country of residence",
layer = 0,
enabled = true -- loaded by default?
}
end
local GetDrawFrame = Spring.GetDrawFrame
local GetVisibleUnits = Spring.GetVisibleUnits
local ALL_UNITS = Spring.ALL_UNITS
local frame, oldframe = 0,0
WG.visible = {}
function widget:DrawWorld()
frame = GetDrawFrame()
if frame > oldframe then
--Spring.Echo("running now",frame)
WG.visible = GetVisibleUnits(ALL_UNITS,nil,false)
oldframe = frame + 10
end
end
Then as a typical example of a use case... here we need to get visible Units to evaluate whether to do some fancy graphics effect, so we use that list like so:
Code: Select all
function widget:DrawWorldPreUnit()
--Set up basic OpenGL parameters here
for i=1,#WG.visible do
local unitID = WG.visible[i]
id = GetUnitDefID(unitID)
if UsesReallyCoolFX[id] then
--display lists, shaders, whatever here
end
end
glResetState()
end
Anyhow, that's pretty much it. It's a simple technique that greatly reduces CPU load.
I saw almost 15 FPS back with P.U.R.E. on a typical "city" map (i.e., high CPU load), which is a huge savings on lower-end hardware, so I thought I'd share this, as it's easy, if boring, to implement it and update your Widgets / Gadgets.
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 24 Aug 2009, 08:25
by hoijui
good

Re: Cutting GetVisibleUnits searches (optimization)
Posted: 25 Aug 2009, 01:53
by Caydr
Health Bars and Commander Names are two common ones that would benefit from this, if indeed there is better performance.
Also CA has these ones:
gfx_halo.lua
gfx_night.lua
gfx_outline.lua
gui_take_remind.lua
~~~~
Stupid question:
Why GetDrawFrame instead of GetGameFrame?
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 25 Aug 2009, 03:12
by lurker
Because you tend to not hold the camera completely still when the game is paused.
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 25 Aug 2009, 20:12
by Caydr
Ahhhh-ha. I see, I see... I was wondering, because I don't see even a single other reference to GetDrawFrame anywhere on the forum.
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 22 Dec 2009, 02:51
by Argh
I wrote an early Gadget version, but it wasn't working right. I left it to one side, fixed it this evening. It's considerably slower than the Widget version, but it's necessary for Gadgets that need to determine valid visibility states. I plan to replace this largely with the LOS bitmap approach eventually, but it's a quick fix for now.
If anybody has any good ideas about where it can be sped up, please let me know, but it works as written.
Code: Select all
function gadget:GetInfo()
return {
name = "00000Visible Units (Gadget Version)",
desc = "Speedup for VisibleUnits() callin",
author = "Argh",
date = "December 21, 2009",
license = "(C) Wolfe Games, 2009, released under GPL v.2",
layer = 0,
enabled = true -- loaded by default?
}
end
if (gadgetHandler:IsSyncedCode()) then
else
local oldframe = 0
GG.visible = {}
function gadget:DrawGenesis()
local ALL_UNITS = Spring.ALL_UNITS
local GetGameFrame = Spring.GetGameFrame
local GetVisibleUnits = Spring.GetVisibleUnits
local GetLocalTeamID = Spring.GetLocalTeamID
local GetUnitLosState = Spring.GetUnitLosState
local myTeam = GetLocalTeamID()
local frame = GetGameFrame()
if frame > oldframe then
GG.visible = {}
local losState
local visTable = GetVisibleUnits(ALL_UNITS)
for i,k in ipairs (visTable) do
losState = Spring.GetUnitLosState(k,myTeam)
losState = losState.los
--Spring.Echo(losState)
if losState == true then
table.insert(GG.visible,k)
end
end
local oldframe = frame + 5
end
end
end
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 22 Dec 2009, 03:29
by jK
Code: Select all
function gadget:GetInfo()
return {
...
}
end
if (gadgetHandler:IsSyncedCode()) then
return
end
local ALL_UNITS = Spring.ALL_UNITS
local GetGameFrame = Spring.GetGameFrame
local GetLocalTeamID = Spring.GetLocalTeamID
local GetUnitLosState = Spring.GetUnitLosState
local OrigGetVisibleUnits = Spring.GetVisibleUnits
local uupdateEachSec = 5
local oldframe = 0
local bufVisibleLists = {}
local tconcat = table.concat
local function ArgumentsToTableIndex(...)
return tconcat({...})
end
local function UpdateVisUnits(...)
local tidx = ArgumentsToTableIndex(...)
bufVisibleLists[tidx] = OrigGetVisibleUnits(...)
end
--// please note, that the following code needs to get executed BEFORE any other gadgets which use those functions! (-> either give the file a smart name or modify the gadgetHandler to load the gadget right at the beginning)
Spring.GetVisibleUnits = function (...)
local tidx = ArgumentsToTableIndex(...)
local bufList = bufVisibleLists[tidx]
local frame = GetGameFrame()
if (not bufList)or(frame > bufList.oldframe) then
CallAsTeam(GetLocalTeamID(),UpdateVisUnits,...)
bufList = bufVisibleLists[tidx]
bufList.oldframe = frame + updateEachSec
end
return bufList
end
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 22 Dec 2009, 06:43
by Niobium
I prefer having it time-based than frame-based. Purely because it makes more sense. Also I have it force an update if the camera position/direction changes, consider case where camera moves rapidly to center on an offscreen unit. Updating every frame is completely fine when it's temporary.
It really does not take much effort to bring the CPU usage to negligible levels. Considering widgets that do it every frame are racking up ~60 calls/second each, one widget that does it 10 times/sec is going to make a huge difference.
The widget I use updates only once per second, but combined with forcing update on camera move, I never see unit that isn't in the visible table.
Also Argh, you shouldn't use ipairs() and table.insert, they're both very slow (ipairs does a nil-check on every single item, and t[#t+1] is faster for end-of-table insert)
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 22 Dec 2009, 11:49
by Argh
Thanks, jK, that should help considerably.
You sure it's going to work without checking the mask state, though?
I couldn't get it to work until I added that safeguard (which is why it's considerably slower than the Widget version, that table operation was sloooow). I was puzzled about that, because it doesn't operate the same way as Widgets do, which is why I had to write that Frankenstein of a loop in a real hurry.
And yeah, it's loaded at the beginning of time already, hehe.
Re: Cutting GetVisibleUnits searches (optimization)
Posted: 22 Dec 2009, 13:09
by jK
Argh wrote:You sure it's going to work without checking the mask state, though?
Code: Select all
Spring.GetVisibleUnits = function (...)
...
if (not bufList)or(frame > bufList.oldframe) then
--vvvvvvvv
CallAsTeam(GetLocalTeamID(),UpdateVisUnits,...)
...
end
return bufList
end
I didn't checked if the used functions obey CallAsTeam, but it _should_ work like this ^^.
Niobium wrote:I prefer having it time-based than frame-based. Purely because it makes more sense. Also I have it force an update if the camera position/direction changes, consider case where camera moves rapidly to center on an offscreen unit. Updating every frame is completely fine when it's temporary.
Code: Select all
function gadget:GetInfo()
return {
...
}
end
if (gadgetHandler:IsSyncedCode()) then
return
end
local ALL_UNITS = Spring.ALL_UNITS
local CallAsTeam = CallAsTeam
local spGetLocalTeamID = Spring.GetLocalTeamID
local spGetTimer = Spring.GetTimer
local spDiffTimers = Spring.DiffTimers
local OrigGetVisibleUnits = Spring.GetVisibleUnits
local updateEachSec = 0.2 --//in seconds!
local oldframe = 0
local bufVisibleLists = {}
local tconcat = table.concat
local function ArgumentsToTableIndex(...)
return tconcat({...})
end
local function UpdateVisUnits(...)
local tidx = ArgumentsToTableIndex(...)
bufVisibleLists[tidx] = OrigGetVisibleUnits(...)
end
--// please note, that the following code needs to get executed BEFORE any other gadgets which use those functions! (-> either give the file a smart name or modify the gadgetHandler to load the gadget right at the beginning)
Spring.GetVisibleUnits = function (...)
local tidx = ArgumentsToTableIndex(...)
local bufList = bufVisibleLists[tidx]
local curTime = spGetTimer()
if (not bufList)or
(spDiffTimers(curTime, bufList.oldtime) > updateEachSec)
then
CallAsTeam(spGetLocalTeamID(),UpdateVisUnits,...)
bufList = bufVisibleLists[tidx]
bufList.oldtime = curTime
end
return bufList
end