Page 1 of 1

How to write a new widget:... callin?

Posted: 27 Feb 2022, 21:44
by Beherith
I have a widget that does a bit of management of what units are visible at any given time. This is just a few hundred lines of code, that check wether they are truly drawn, and tracks them incrementally.

I would like this widget to implement a few new widget:VisibleUnitAdd, widget:VisibleUnitRemove, etc calls.

Does anyone have a bit of info, or maybe an example of how to do this correctly? As I have a good dozen widget that use these calls instead of repeating a ton of boilerplate code.

Re: How to write a new widget:... callin?

Posted: 28 Feb 2022, 17:42
by Silentwings
Seems like a clean one - https://github.com/spring/spring/commit ... 880d2d4dc2

Or did you mean add it as a "fake" lua-powered callin to the widgethandler without engine interaction?

But from your use case (which I don't completely follow) I wonder if you'd be better off with a callout e.g. Spring.GetVisible(Unit,Feature)s / Spring.(Get,Set)(Unit,Feature)Visibility

Re: How to write a new widget:... callin?

Posted: 01 Mar 2022, 13:23
by Beherith
Thanks Silentwings, that is very good info for a gadget-side engine one. Ive written a similar callin into barspring for AllowUnitCaptureStep() .

But I needed a purely Lua widget-side one: Watch The Fort and Sprung/Sprunk assisted me in this one:

Code: Select all

Beyond-All-Reason.sdd\luaui\barwidgets.lua
  174:     'LanguageChanged', -- add to list of callins
 1639: function widgetHandler:LanguageChanged()
 1640:     for _, w in ipairs(self.LanguageChangedList) do
 1641:         w:LanguageChanged()

Trigger the callin from a widget:

Code: Select all

C:\Users\Peti\Documents\My Games\Spring\games\Beyond-All-Reason.sdd\luaui\Widgets\gui_language.lua
   54:         if Script.LuaUI('LanguageChanged') then
   55:             Script.LuaUI.LanguageChanged()

Handle the callin in another widget:

Code: Select all

C:\Users\Peti\Documents\My Games\Spring\games\Beyond-All-Reason.sdd\luaui\Widgets\gui_info.lua
 1561: function widget:LanguageChanged()
My use case is the following: for GL4 widgets, especially widgets that draw stuff at units, its best to have a vertex buffer object that always contains the things we need to draw, and is updated incrementally whenever new units enter los/get created or old units die/go out of los.

This way, the widgets dont have to do any work to do occlusion culling ever, its all done GPU side.

This whole new widget callin system was needed to reduce the 100 lines of boilerplate code on each widget which tracked the nasty edge cases of unitgiven, unittaken, the even nastier cases where a zombie unit (no longer alive but not invalid yet) could enter LOS, and all the management of rebuilding the table of valid visible units on PlayerChanged callins.

Using all of this stuff, making a widget that draws something at every single unit with 0 cpu overhead gets simplified to:

Code: Select all

function widget:GetInfo()
   return {
      name      = "API Unit Tracker Tester GL4",
      desc      = "Tracks visibleunitslist",
      author    = "Beherith",
      date      = "2022.03.01",
      license   = "GNU GPL, v2 or later",
      layer     = -88888888,
      enabled   = true
   }
end

local myvisibleUnits = {} -- table of unitID : unitDefID

local unitTrackerVBO = nil
local unitTrackerShader = nil
local luaShaderDir = "LuaUI/Widgets/Include/"
local texture = "luaui/images/solid.png"

local function initGL4()
	local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua")
	local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit
	local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua
	shaderConfig.TRANSPARENCY = 0.5	
	shaderConfig.ANIMATION = 0
	shaderConfig.HEIGHTOFFSET = 3.99
	unitTrackerVBO, unitTrackerShader = InitDrawPrimitiveAtUnit(shaderConfig, "unitTrackerTester")
end


function widget:VisibleUnitAdded(unitID, unitDefID, unitTeam)
	Spring.Echo("widget:VisibleUnitAdded",unitID, unitDefID, unitTeam)
	local teamID = Spring.GetUnitTeam(unitID) or 0 
	local gf = Spring.GetGameFrame()
	myvisibleUnits[unitID] = unitDefID
	pushElementInstance(
		unitTrackerVBO, -- push into this Instance VBO Table
		{
			96, 96, 8, 8,  -- lengthwidthcornerheight
			teamID, -- teamID
			12, -- how many trianges should we make (2 = cornerrect)
			gf, 0, 0, 0, -- the gameFrame (for animations), and any other parameters one might want to add
			0, 1, 0, 1, -- These are our default UV atlas tranformations
			0, 0, 0, 0 -- these are just padding zeros, that will get filled in
		},
		unitID, -- this is the key inside the VBO TAble,
		true, -- update existing element
		nil, -- noupload, dont use unless you know what you are doing
		unitID -- last one should be UNITID?
	)
end

function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits)
	Spring.Echo("widget:VisibleUnitsChanged",extVisibleUnits, extNumVisibleUnits)
	clearInstanceTable(unitTrackerVBO)
	for unitID, unitDefID in pairs(extVisibleUnits) do 
		widget:VisibleUnitAdded(unitID, unitDefID, Spring.GetUnitTeam(unitID))
	end
end

function widget:VisibleUnitRemoved(unitID)
	Spring.Echo("widget:VisibleUnitRemoved",unitID)
	if unitTrackerVBO.instanceIDtoIndex[unitID] then 
		popElementInstance(unitTrackerVBO, unitID)
		myvisibleUnits[unitID] = nil
	end
end

function widget:DrawWorldPreUnit()
	if Spring.IsGUIHidden() then
		return
	end

	if unitTrackerVBO.usedElements > 0 then
		gl.Texture(0, texture)
		unitTrackerShader:Activate()
		unitTrackerShader:SetUniform("iconDistance", 99999) -- pass
		unitTrackerShader:SetUniform("addRadius", 0)
		gl.DepthTest(true)
		gl.DepthMask(false)
		unitTrackerVBO.VAO:DrawArrays(GL.POINTS, unitTrackerVBO.usedElements)
		unitTrackerShader:Deactivate()
		gl.Texture(0, false)
	end
end

function widget:Initialize()
	initGL4()
end
Edit: All of this is allowing us single digit LuaUI load even in heavy lategame situations, and most of that goes to UI element rendering :)
With all the bells and whistles enabled, like gpu rendered healthbars, paralyze effects, stenciled LOS and radar ranges, rank icons, AO plates for features, AO plates for units etc.