Instances within other instances

daviiadams

Prince
Joined
Jul 7, 2011
Messages
369
Location
London
What's the best way to go about instances within other instances?

I've a whole pile of things that lists out things for each civ, but find I either have to give each civ it's own stack, end up with every civ's instances on one long entry, or nothing appearing at all :confused:

I'd gone on the idea that I grab two instances, one for the civ loop, followed by a second inside the following loop that contains each individual instance for that civ - am I in the right ball park?
 
If you want to say, loop through all units for all players then you would have an outside 'players' loop and an inside 'units' loop. If that's what you are asking? What are you trying to do, and where?

But how would you represent those units as individual values/icons/whatever, without all players results appearing as one long line of values, or needing to put in xml for every player/unit combination (to use the unit example mentioned)?

I've got that kind of thing in several places in a mod, so taking that unit example; Once all the units for the first player have been delt with, how do you then move to the next line, for the next player, instead of them all following on from the previous player?

As in:

Player 1: a b c d e
Player 2: a b c d
Player 3: a b c d e f g

Instead of:

Player 1: a b c d e a b c d a b c d e f g

:confused:

I can do it, but only if I don't use InstanceManager and list out individual placements in the xml for each player, which appears to be restrictive and fills the xml file with a lot of repetition.
 
Here is a sample player/unit loop in one of my mods:

Code:
        for _, v in pairs(Players) do
		if v:IsHuman() then
			for u in v:Units() do
				if not u:IsCombatUnit() then
					gUnEscorted[u] = u:GetPlot()
				end
			end
		end
	end

Which returns the plot of every unescorted non-combat unit for the human player (?)

How would you then display the plot ID (for example) for all unescorted units, for all players in the UI?

So you end up with:

Player 1: U U U
Player 2: U U
Player 3: U U U U

Where each "U" is the plot ID for each unescorted unit, for that player. At the moment, I would end up with s result like:

Player 1: U U U U U U U U U
Player 2:
Player 3:

Obviously, you'd want the instances of plot IDs for the unescorted units to appear next to their relivant player, not all in one continuous row attached to the first player entry and it's that "moving to the next line" bit that eludes me.
 
I'm guessing from you use of the term "InstanceManager" we're talking UI controls - ie stacks within stacks.

It's non trivial, I'll put an example together
 
Indeed, apologies for the tired waffling in trying to explain myself :-S

I had initially thought I'd need an instance for both the civ and its correspinding instance entries, but now wondering if I just create a table for each set of instances to sit in (along the same lines as how the player colours for maps are done) and something something something :crazyeye:
 
My fault. I tend to assume people are looking for lua answers and that led me to think your mention of instance was just a random word confusion for [generic instance of a class], e.g. units, players. In this case it was my ignorance and instance actually meant something specific. So, I have learned, thank you :)

I have a tendency to ask questions when I've been at it for hours and should really be going to sleep, not expecting people to understand my frazzled brain :p
 
StackedStacks.xml

Code:
<?xml version="1.0" encoding="UTF-8"?>
<Context Font="TwCenMT16" FontStyle="Base">
  <!-- The Civilisation instance - icon plus list of units -->
  <Instance Name="CivInstance">
    <Box ID="CivRoot" Anchor="L,T" Size="424,64" Color="0,0,0,255" >
      <Image ID="CivIconBG" Size="64,64" Anchor="L,T" TextureOffset="77,0" Texture="CivIconBGSizes.dds">
        <Image ID="CivIconShadow" Size="48,48" Anchor="C,C" Texture="CivSymbolAtlas48.dds" Color="Black,128"/>
        <Image ID="CivIcon" Size="48,48" Offset="-1,-1" Anchor="C,C" Texture="CivSymbolsColor512.dds" />
      </Image>

      <!-- Scroll panels within scroll panels fundamentally don't work -->
      <!-- Normal controls (text, images, boxes, etc) within a scroll panel are cropped to fit, but -->
      <!-- if ANY part of a scroll panel is visible the entire panel is shown -->
      <!-- which causes the inner panel to "bleed" outside the limits of the containing panel -->
      <ScrollPanel Anchor="L,T" ID="UnitPanel" Vertical="0" Offset="64,0" Size="360,64" AutoScrollBar="1">
        <UpButton   Anchor="L,B" Offset="0,0" Style="ScrollBarLeft"/>
        <ScrollBar  Anchor="L,B" Offset="18,0" Length="324" Style="ScrollBar"/>
        <!-- We cannot do the usual R,B alignment here as the RB corner is not always in view in the scroll panel-->
        <DownButton Anchor="L,B" Offset="342,0" Style="ScrollBarRight"/>

        <Stack ID="UnitStack" Anchor="L,T" Padding="0" StackGrowth="Right"/>
      </ScrollPanel>
    </Box>
  </Instance>

  <!-- The Unit instance - just the icon, we'll add a tooltip via Lua -->
  <Instance Name="UnitInstance">
    <Box ID="UnitRoot" Anchor="L,T" Size="45,45" Color="Black,255" >
      <Image ID="UnitIcon" Anchor="C,C" Size="45,45"/>
    </Box>
  </Instance>

  <!-- The main bounding box and scroll panel -->
  <Box Size="468,580" Anchor="C,C" Offset="0,0" Color="White,255">
    <Box Size="464,576" Anchor="C,C" Offset="0,0" Color="Black,255">
      <ScrollPanel Anchor="L,T" ID="CivPanel" Vertical="1" Size="464,576" AutoScrollBar="1">
        <UpButton   Anchor="R,T" Offset="2,2" Style="ScrollBarUp"/>
        <ScrollBar  Anchor="R,T" Offset="2,20" Length="536" Style="VertSlider"/>
        <DownButton Anchor="R,B" Offset="2,2" Style="ScrollBarDown"/>

        <Stack ID="CivStack" StackGrowth="B" Offset="0,0" Padding="0" />
      </ScrollPanel>
    </Box>
  </Box>
</Context>

StackedStacks.lua

Code:
--
-- A stack (civs) of stacks (units per civ)
--
-- Also illustrates why scroll panels within scroll panels are a complete nightmare!
--

-- Bring in the standard support utilities
include("IconSupport")
include("InstanceManager")

-- For each player, we need to track both the entry in the civ stack and the unit instance manager, so we'll need somewhere to put that information
local g_PlayerEntries = {}


function UpdateUnits()
  -- Hide everything in the current list of civs (will be nothing on the first invocation)
  for _, playerEntry in pairs(g_PlayerEntries) do
    playerEntry.civInstance.CivRoot:SetHide(true)
  end

  -- For every player that is still alive ...
  for iPlayer = 0, GameDefines.MAX_CIV_PLAYERS-1, 1 do
    local pPlayer = Players[iPlayer]
  
    if (pPlayer:IsEverAlive() and pPlayer:IsAlive()) then
      local playerEntry = g_PlayerEntries[iPlayer]

      -- Have we processed this player before?
      if (playerEntry == nil) then
        -- No, so create an array entry for them
        playerEntry = {}
        g_PlayerEntries[iPlayer] = playerEntry

        -- Create an entry for them in the stack of civs ...
        -- (Note that we are NOT using an InstanceManager for this,
        --  we must use the same unit IM every time for this entry,
        --  so we'll just manage both the entry and the unit IM ourselves)
        playerEntry.civInstance = {}
        ContextPtr:BuildInstanceForControl("CivInstance", playerEntry.civInstance, Controls.CivStack)

        -- ... and fill in the details ...
        CivIconHookup(iPlayer, 64, playerEntry.civInstance.CivIcon, playerEntry.civInstance.CivIconBG, playerEntry.civInstance.CivIconShadow, false, true);
        playerEntry.civInstance.CivRoot:SetHide(false) -- unnecessary unless Hidden="1" has been applied in the XML
        playerEntry.civInstance.CivIconBG:SetToolTipString(Locale.ConvertTextKey(pPlayer:GetCivilizationShortDescriptionKey()))

        -- ... and hook up a unit instance manager
          playerEntry.unitIM = InstanceManager:new("UnitInstance", "UnitRoot", playerEntry.civInstance.UnitStack)
      else
        -- Yes, so clear out any old units ...
        playerEntry.unitIM:ResetInstances()

        -- ... and show the entry (it was hidden in the loop at the beginning)
        playerEntry.civInstance.CivRoot:SetHide(false)
      end

      -- Fill in the current list of units for this player, standard stack filling stuff via an IM
      for pUnit in pPlayer:Units() do
        local unitInstance = playerEntry.unitIM:GetInstance()
        local unitInfo = GameInfo.Units[pUnit:GetUnitType()]
        IconHookup(unitInfo.PortraitIndex, 45, unitInfo.IconAtlas, unitInstance.UnitIcon)
        unitInstance.UnitIcon:SetToolTipString(pUnit:GetName())
      end

      -- Adjust the size of the unit stack and scroll panel (note that scroll panels within scroll panels do not work!)
      playerEntry.civInstance.UnitStack:CalculateSize();
      playerEntry.civInstance.UnitStack:ReprocessAnchoring();
      playerEntry.civInstance.UnitPanel:CalculateInternalSize()
    end
  end

  -- Adjust the size of the civ stack and scroll panel
  Controls.CivStack:CalculateSize();
  Controls.CivStack:ReprocessAnchoring();
  Controls.CivPanel:CalculateInternalSize()
end


function Show()
  ContextPtr:SetHide(false)
  UpdateUnits()
end

function Hide()
  ContextPtr:SetHide(true)
end

Show()
 
StackedStacks.xml

Code:
<?xml version="1.0" encoding="UTF-8"?>
<Context Font="TwCenMT16" FontStyle="Base">
  <!-- The Civilisation instance - icon plus list of units -->
  <Instance Name="CivInstance">
    <Box ID="CivRoot" Anchor="L,T" Size="424,64" Color="0,0,0,255" >
      <Image ID="CivIconBG" Size="64,64" Anchor="L,T" TextureOffset="77,0" Texture="CivIconBGSizes.dds">
        <Image ID="CivIconShadow" Size="48,48" Anchor="C,C" Texture="CivSymbolAtlas48.dds" Color="Black,128"/>
        <Image ID="CivIcon" Size="48,48" Offset="-1,-1" Anchor="C,C" Texture="CivSymbolsColor512.dds" />
      </Image>

      <!-- Scroll panels within scroll panels fundamentally don't work -->
      <!-- Normal controls (text, images, boxes, etc) within a scroll panel are cropped to fit, but -->
      <!-- if ANY part of a scroll panel is visible the entire panel is shown -->
      <!-- which causes the inner panel to "bleed" outside the limits of the containing panel -->
      <ScrollPanel Anchor="L,T" ID="UnitPanel" Vertical="0" Offset="64,0" Size="360,64" AutoScrollBar="1">
        <UpButton   Anchor="L,B" Offset="0,0" Style="ScrollBarLeft"/>
        <ScrollBar  Anchor="L,B" Offset="18,0" Length="324" Style="ScrollBar"/>
        <!-- We cannot do the usual R,B alignment here as the RB corner is not always in view in the scroll panel-->
        <DownButton Anchor="L,B" Offset="342,0" Style="ScrollBarRight"/>

        <Stack ID="UnitStack" Anchor="L,T" Padding="0" StackGrowth="Right"/>
      </ScrollPanel>
    </Box>
  </Instance>

  <!-- The Unit instance - just the icon, we'll add a tooltip via Lua -->
  <Instance Name="UnitInstance">
    <Box ID="UnitRoot" Anchor="L,T" Size="45,45" Color="Black,255" >
      <Image ID="UnitIcon" Anchor="C,C" Size="45,45"/>
    </Box>
  </Instance>

  <!-- The main bounding box and scroll panel -->
  <Box Size="468,580" Anchor="C,C" Offset="0,0" Color="White,255">
    <Box Size="464,576" Anchor="C,C" Offset="0,0" Color="Black,255">
      <ScrollPanel Anchor="L,T" ID="CivPanel" Vertical="1" Size="464,576" AutoScrollBar="1">
        <UpButton   Anchor="R,T" Offset="2,2" Style="ScrollBarUp"/>
        <ScrollBar  Anchor="R,T" Offset="2,20" Length="536" Style="VertSlider"/>
        <DownButton Anchor="R,B" Offset="2,2" Style="ScrollBarDown"/>

        <Stack ID="CivStack" StackGrowth="B" Offset="0,0" Padding="0" />
      </ScrollPanel>
    </Box>
  </Box>
</Context>

StackedStacks.lua

Code:
--
-- A stack (civs) of stacks (units per civ)
--
-- Also illustrates why scroll panels within scroll panels are a complete nightmare!
--

-- Bring in the standard support utilities
include("IconSupport")
include("InstanceManager")

-- For each player, we need to track both the entry in the civ stack and the unit instance manager, so we'll need somewhere to put that information
local g_PlayerEntries = {}


function UpdateUnits()
  -- Hide everything in the current list of civs (will be nothing on the first invocation)
  for _, playerEntry in pairs(g_PlayerEntries) do
    playerEntry.civInstance.CivRoot:SetHide(true)
  end

  -- For every player that is still alive ...
  for iPlayer = 0, GameDefines.MAX_CIV_PLAYERS-1, 1 do
    local pPlayer = Players[iPlayer]
  
    if (pPlayer:IsEverAlive() and pPlayer:IsAlive()) then
      local playerEntry = g_PlayerEntries[iPlayer]

      -- Have we processed this player before?
      if (playerEntry == nil) then
        -- No, so create an array entry for them
        playerEntry = {}
        g_PlayerEntries[iPlayer] = playerEntry

        -- Create an entry for them in the stack of civs ...
        -- (Note that we are NOT using an InstanceManager for this,
        --  we must use the same unit IM every time for this entry,
        --  so we'll just manage both the entry and the unit IM ourselves)
        playerEntry.civInstance = {}
        ContextPtr:BuildInstanceForControl("CivInstance", playerEntry.civInstance, Controls.CivStack)

        -- ... and fill in the details ...
        CivIconHookup(iPlayer, 64, playerEntry.civInstance.CivIcon, playerEntry.civInstance.CivIconBG, playerEntry.civInstance.CivIconShadow, false, true);
        playerEntry.civInstance.CivRoot:SetHide(false) -- unnecessary unless Hidden="1" has been applied in the XML
        playerEntry.civInstance.CivIconBG:SetToolTipString(Locale.ConvertTextKey(pPlayer:GetCivilizationShortDescriptionKey()))

        -- ... and hook up a unit instance manager
          playerEntry.unitIM = InstanceManager:new("UnitInstance", "UnitRoot", playerEntry.civInstance.UnitStack)
      else
        -- Yes, so clear out any old units ...
        playerEntry.unitIM:ResetInstances()

        -- ... and show the entry (it was hidden in the loop at the beginning)
        playerEntry.civInstance.CivRoot:SetHide(false)
      end

      -- Fill in the current list of units for this player, standard stack filling stuff via an IM
      for pUnit in pPlayer:Units() do
        local unitInstance = playerEntry.unitIM:GetInstance()
        local unitInfo = GameInfo.Units[pUnit:GetUnitType()]
        IconHookup(unitInfo.PortraitIndex, 45, unitInfo.IconAtlas, unitInstance.UnitIcon)
        unitInstance.UnitIcon:SetToolTipString(pUnit:GetName())
      end

      -- Adjust the size of the unit stack and scroll panel (note that scroll panels within scroll panels do not work!)
      playerEntry.civInstance.UnitStack:CalculateSize();
      playerEntry.civInstance.UnitStack:ReprocessAnchoring();
      playerEntry.civInstance.UnitPanel:CalculateInternalSize()
    end
  end

  -- Adjust the size of the civ stack and scroll panel
  Controls.CivStack:CalculateSize();
  Controls.CivStack:ReprocessAnchoring();
  Controls.CivPanel:CalculateInternalSize()
end


function Show()
  ContextPtr:SetHide(false)
  UpdateUnits()
end

function Hide()
  ContextPtr:SetHide(true)
end

Show()

Thanks for the example, I'll be able to compare it to where I got to yesterday after realising that some kind of overall table would be required, though the underscore would not have come to mind at all.

I may leave a lot of stuff in BPI as it is (I'd planned to switch a lot of it over to using InstanceManager) but there's one or two places where I get the feeling this is the only way to do things, outside of endless repitition in the xml file :)
 
Which returns the plot of every unescorted non-combat unit for the human player (?)

How would you then display the plot ID (for example) for all unescorted units, for all players in the UI?

So you end up with:

Player 1: U U U
Player 2: U U
Player 3: U U U U

Where each "U" is the plot ID for each unescorted unit, for that player. At the moment, I would end up with s result like:

Player 1: U U U U U U U U U
Player 2:
Player 3:

Obviously, you'd want the instances of plot IDs for the unescorted units to appear next to their relivant player, not all in one continuous row attached to the first player entry and it's that "moving to the next line" bit that eludes me.
Just remove the is human. It's just a sparsely populated array. { unitID = PlotID }

You don't need to store the player. Unit id's are unique across all players, and you can get the owner (player) from either the unit or the plot.

But if you truly had a need for the kind of table you are talking about, you could do something like:

Code:
       for p, v in pairs(Players) do
           for u in v:Units() do
		if not u:IsCombatUnit() then
			gUnEscorted[] = { p, u }
		end
	   end
	end

This makes an autoincrement array with the values being another array or player and unit (if I remembered my syntax right off the top of my head). In this sene you can think of each entry in the array as a row. Actually, you can in either case. The first is a row with only one value in it. Concept wise (say compared to a database table) it's still key/index = value(s).
 
I'm not the best one to comment on the different possibilities, as my knowledge is limited, but it's when those rows contain several values each, that are wanted to be displayed in the UI is where it appears to get a lot more involved :confused:
 
I can see how it's working, but wouldn't be able to articulate that clearly to anyone else. Heck, I can't even post screen shots :crazyeye:
 
array[] = whatever

Does not appear to work in lua (my PHP is showing). So, you'd need to add an iterator. e.g. array = whatever


"Use the library Luke!"

In this case the table library

table.insert(array, whatever)
 
Out of interest, is it not necessary to destroy all children for the build instance for control part in that example?
 
Out of interest, is it not necessary to destroy all children for the build instance for control part in that example?

No, because we re-use them.

First time around, add a new instance for each civ, and remember which civ got what instance

Second time around, hide all the instances (in case a civ has been killed) and then for each civ that is still alive unhide their specific instance and reuse it
 
No, because we re-use them.

First time around, add a new instance for each civ, and remember which civ got what instance

Second time around, hide all the instances (in case a civ has been killed) and then for each civ that is still alive unhide their specific instance and reuse it

And it doesn't matter if the specific instances change from one turn to the next?
 
Top Bottom