Cutting GetVisibleUnits searches (optimization)

Cutting GetVisibleUnits searches (optimization)

Discuss Lua based Spring scripts (LuaUI widgets, mission scripts, gaia scripts, mod-rules scripts, scripted keybindings, etc...)

Moderator: Moderators

Post Reply
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Cutting GetVisibleUnits searches (optimization)

Post 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.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Cutting GetVisibleUnits searches (optimization)

Post by hoijui »

good :-)
User avatar
Caydr
Omnidouche
Posts: 7179
Joined: 16 Oct 2004, 19:40

Re: Cutting GetVisibleUnits searches (optimization)

Post 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?
Last edited by Caydr on 25 Aug 2009, 02:41, edited 1 time in total.
User avatar
lurker
Posts: 3842
Joined: 08 Jan 2007, 06:13

Re: Cutting GetVisibleUnits searches (optimization)

Post by lurker »

Because you tend to not hold the camera completely still when the game is paused.
User avatar
Caydr
Omnidouche
Posts: 7179
Joined: 16 Oct 2004, 19:40

Re: Cutting GetVisibleUnits searches (optimization)

Post 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.
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Re: Cutting GetVisibleUnits searches (optimization)

Post 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
User avatar
jK
Spring Developer
Posts: 2299
Joined: 28 Jun 2007, 07:30

Re: Cutting GetVisibleUnits searches (optimization)

Post 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
User avatar
Niobium
Posts: 456
Joined: 07 Dec 2008, 02:35

Re: Cutting GetVisibleUnits searches (optimization)

Post 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)
User avatar
Argh
Posts: 10920
Joined: 21 Feb 2005, 03:38

Re: Cutting GetVisibleUnits searches (optimization)

Post 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.
User avatar
jK
Spring Developer
Posts: 2299
Joined: 28 Jun 2007, 07:30

Re: Cutting GetVisibleUnits searches (optimization)

Post 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
Post Reply

Return to “Lua Scripts”