Allocating Zero or Less Memory in CvXMLLoadUtility...

Afforess

The White Wizard
Joined
Jul 31, 2007
Messages
12,239
Location
Austin, Texas
I just finished compiling a brand new DLL, and got this lovely error.

Error Message:
Civilization4 said:
"Allocating zero or less memory in
CvXMLLoadUltility::SetVariableListTagPair
Current XML file is :xml\Buildings/CIV4BuidingInfos.xml"

I compiled a debug version, and had VS start BTS.exe up, debugging, but it still gave me this error and no other information. I just added some new tags to the CvBuildingInfos, but I can see whats wrong with them... What does this error message mean?
 
It means that you are trying to get an array loaded:

Code:
<UnitAffectBuildingThingies>
    <UnitAffectBuildingThing>
        <UnitType>UNIT_DUCK</UnitType>
        <AffectThingieAmount>20</AffectThingieAmount>
    </UnitAffectBuildingThing>
</UnitAffectBuildingThingies>

Something along those lines. Those are what the TagListPair are used to load.

Anyway, you are trying to load one of those which will be size of getNum___Infos, before the ___Infos have actually been loaded. Thus you need to move to a readpass2 or 3. So check CvXMLUtilitySet for the order of info loading against whatever arrays you just added to buildings.
 
It means that you are trying to get an array loaded:

Code:
<UnitAffectBuildingThingies>
    <UnitAffectBuildingThing>
        <UnitType>UNIT_DUCK</UnitType>
        <AffectThingieAmount>20</AffectThingieAmount>
    </UnitAffectBuildingThing>
</UnitAffectBuildingThingies>
Something along those lines. Those are what the TagListPair are used to load.

Anyway, you are trying to load one of those which will be size of getNum___Infos, before the ___Infos have actually been loaded. Thus you need to move to a readpass2 or 3. So check CvXMLUtilitySet for the order of info loading against whatever arrays you just added to buildings.

Yep, looks like your right. I added Civic Checks to CvBuildingInfo, but Civics are loaded after buildings. So, this is just a regular array, but how do I change it to be in CvBuilding ReadPass2.... CvBuilding ReadPass2 doesn't even exist as far as I know...
 
Wait, I just remembered. I could solve this entire problem by moving my arrays over to CvCivicInfo. I suppose it would be kind of weird though, since it's prereqCivic arrays. Which do you recommend, adding a readpass2, or moving the arrays?
 
YMMV, but I found it easier to swap the array to the other file, rather than figuring out the readpass2,3 stuff. More information in this thread.

Hmm. Well, that presents it's own problems, since what I added doesn't easily translate to civics.

What I added was two arrays,

CivicOrPrereqs
CivicAndPrereq's

In CvBuildingInfo. I haven't the slightest idea how to change that into something meaningful in CvCivicInfo... I kind of like the set up I have now.

So, I guess I'll have to add a readpass2 to CvBuildingInfo. Can I get a primer guide or is there a good example of this in the code already?
 
Well, if you used to have object A containing a list of object B's, it seems simple enough to swap to have object B containing a list of object A's. So you could add BuildingOrPrereq and BuildingAndPrereq to CivicInfo. I agree this would be a little odd to read, but it is possible.
 
Well, if you used to have object A containing a list of object B's, it seems simple enough to swap to have object B containing a list of object A's. So you could add BuildingOrPrereq and BuildingAndPrereq to CivicInfo. I agree this would be a little odd to read, but it is possible.

eh, I just added a readpass2. It compiled CvInfos, just a moment ago, so I'm crossing my fingers and hope that it works.
 
Okay, I added a readpass2, and it seems to be working. The game loaded up.

However, my changes to "CanConstruct" in CvCity don't seem to work. I added a requirement for either Slavery or Despotism to build a building, but I could build it without either. The trouble is, I can't tell if it's because my logic is bad, or the readpass2 isn't working. Could someone please tell me which it is?

ReadPass2:

Code:
bool CvBuildingInfo::readPass2(CvXMLLoadUtility* pXML)
{

	if (!CvInfoBase::read(pXML))
	{
		return false;
	}

	pXML->SetVariableListTagPair(&m_pbPrereqOrCivics, "PrereqOrCivics", sizeof(GC.getCivicInfo((CivicTypes)0)), GC.getNumCivicInfos()); //Afforess
	pXML->SetVariableListTagPair(&m_pbPrereqAndCivics, "PrereqAndCivics", sizeof(GC.getCivicInfo((CivicTypes)0)), GC.getNumCivicInfos());

	return true;
}
I also added a NonCopyDefaultsReadPass2 for WOC, after the first NonCopyDefaults.

Code:
void CvBuildingInfo::copyNonDefaultsReadPass2(CvBuildingInfo* pClassInfo)
{
	bool bDefault = false;
	int iDefault = 0;
	int iTextDefault = -1;  //all integers which are TEXT_KEYS in the xml are -1 by default
	int iAudioDefault = -1;  //all audio is default -1	
	float fDefault = 0.0f;
	CvString cDefault = CvString::format("").GetCString();
	CvWString wDefault = CvWString::format(L"").GetCString();
	
	for ( int iCivic = 0; iCivic < GC.getNumCivicInfos(); iCivic++) //Afforess
	{
		if ( isPrereqOrCivics(iCivic) == bDefault )
		{
			m_pbPrereqOrCivics[iCivic] = pClassInfo->isPrereqOrCivics(iCivic);
		}
	}
	
	for ( int iCivic = 0; iCivic < GC.getNumCivicInfos(); iCivic++)
	{
		if ( isPrereqAndCivics(iCivic) == bDefault )
		{
			m_pbPrereqAndCivics[iCivic] = pClassInfo->isPrereqAndCivics(iCivic);
		}
	}
}

Now, the only other code I added was in CanConstruct in CvCity. The code is at the very end of the function, after the python callback code, but before the final "Return True;" code.

Code:
	bool bValidOrCivic = false;
	bool bNoReqOrCivic = true;
	bool bValidAndCivic = true;
	bool bReqAndCivic = true;
	for (iI = 0; iI < GC.getNumCivicInfos(); iI++)
	{
		if (GC.getBuildingInfo(eBuilding).getPrereqOrCivics(iI))
		{
			bNoReqOrCivic = false;
			if (GET_PLAYER(getOwnerINLINE()).isCivic(CivicTypes(iI)))
			{
				bValidOrCivic = true;
			}
		}
		
		if (GC.getBuildingInfo(eBuilding).getPrereqAndCivics(iI))
		{
			bReqAndCivic = true;
			if (!GET_PLAYER(getOwnerINLINE()).isCivic(CivicTypes(iI)))
			{
				bValidAndCivic = false;
			}
		}
	}
	
	if (!bNoReqOrCivic && !bValidOrCivic)
	{
		return false;
	}

	if (bReqAndCivic && !bValidAndCivic)
	{
		return false;
	}
 
Update:

I just put some code in CvGameTextMgr for Civic Prereqs. Since nothing is still showing up in game for those buildings, I would imagine that my readpass2 is not working properly. Any ideas on what I did wrong?
 
You need to nodify one line in CvXMLUtilitySet to have the second pass be used/called. Look for where it loads "CIV4BuildingInfos" and enable readpass2 on that line (compare the line with another file that already uses readpass2, like unitinfos)
 
You need to nodify one line in CvXMLUtilitySet to have the second pass be used/called. Look for where it loads "CIV4BuildingInfos" and enable readpass2 on that line (compare the line with another file that already uses readpass2, like unitinfos)

You mean like this?

Code:
    for (int i=0; i < GC.getNumUnitClassInfos(); ++i)
    {
        GC.getUnitClassInfo((UnitClassTypes)i).readPass3();
    }

That's the unit code? I didn't see any of those for a readpass2. What does that mean?
 
For that function you should be using CvPlayer::canConstruct and not CvCity::canConstruct. It may seem like there is no difference, but I found I could not get proper behavior in CvCity or CvPlot ::canTrain for bStateReligion, and had to add some logic to CvPlayer::canConstruct. For civics it would seem you'd get the same issues.
 
For that function you should be using CvPlayer::canConstruct and not CvCity::canConstruct. It may seem like there is no difference, but I found I could not get proper behavior in CvCity or CvPlot ::canTrain for bStateReligion, and had to add some logic to CvPlayer::canConstruct. For civics it would seem you'd get the same issues.

Okay, I'll move my code over there. Thanks for the heads up.
 
You need to change:
Code:
LoadGlobalClassInfo(GC.getSpecialBuildingInfo(), "CIV4SpecialBuildingInfos", "Buildings", "Civ4SpecialBuildingInfos/SpecialBuildingInfos/SpecialBuildingInfo", false);

To be like:

Code:
LoadGlobalClassInfo(GC.getTerrainInfo(), "CIV4TerrainInfos", "Terrain", "Civ4TerrainInfos/TerrainInfos/TerrainInfo", true);

Turns out Units didn't use a readpass2 in native BtS code, because upgrades are through UnitCLASS. Forgot that detail.

So it is even easier than I thought it would be. Just go to that line and change the final false to a true.
 
I get a CTD with that change. I compiled a debug dll, and saw where it was breaking.
Code:
template <class T>
void CvXMLLoadUtility::SetGlobalClassInfoTwoPassReplacement(std::vector<T*>& aInfos, const char* szTagName)
{
	char szLog[256];
	sprintf(szLog, "SetGlobalClassInfo (%s)", szTagName);
	PROFILE(szLog);
	logMsg(szLog);

	if (gDLL->getXMLIFace()->LocateNode(m_pFXml, szTagName))
	{
		// loop through each tag
		do
		{
			SkipToNextVal();	// skip to the next non-comment node

			T* pClassInfo = new T;

			FAssert(NULL != pClassInfo);

			bool bSuccess = pClassInfo->readPass2(this);

			if ( !GC.isAnyDependency() )
			{
				FAssert(bSuccess);
			}
			else
			{
				int iTypeIndex = -1;
				if (NULL != pClassInfo->getType())
				{
					iTypeIndex = GC.getInfoTypeForString(pClassInfo->getType(), true);
				}

				// TYPE dependency? (Condition 1)
				if ( GC.getTypeDependency() && -1 == iTypeIndex)
				{
					delete pClassInfo;
					GC.resetDependencies();		// make sure we always reset once anydependency was true!
					continue;
				}

				// OR Dependencies (Condition 2)
				GC.setTypeDependency(false);
				if ( GC.getOrNumDependencyTypes() > 0 )
				{
					// if we have Or dependencies, set to dependend by default(this will prevent loading)
					// the moment ANY condition is met, we can safely load the ClassInfo and set the
					// dependency to false
					GC.setTypeDependency(true);
				}
				for ( int iI = 0; iI < GC.getOrNumDependencyTypes(); iI++ )
				{
					iTypeIndex = GC.getInfoTypeForString( GC.getOrDependencyTypes(iI), true );
					if ( !(iTypeIndex == -1) )
					{
						// we found a OR dependent Type, so we can load safely!
						// dependency will be set disabled(false)
						GC.setTypeDependency(false);
					}
				}

				for ( int iI = 0; iI < GC.getAndNumDependencyTypes(); iI++ )
				{
					iTypeIndex = GC.getInfoTypeForString( GC.getAndDependencyTypes(iI), true );
					if ( iTypeIndex == -1 )
					{
						// if any AND condition is not met, we disable the loading of the Class Info!
						GC.setTypeDependency(true);
					}
				}

				//This covers both the bTypeDependency and the And/Or-DependencyTypes tags!
				if ( GC.getTypeDependency() )
				{
					// We found that any of the 3! conditions NOT to load this class info has been met!
					delete pClassInfo;
					GC.resetDependencies();		// make sure we always reset once anydependency was true!
					continue;
				}
				else
				{
					bool bSuccess = pClassInfo->read(this);
					GC.resetDependencies();		// make sure we always reset once anydependency was true!
					FAssert(bSuccess);
					if (!bSuccess)
					{
						delete pClassInfo;
						break;
					}
				}
			}

			int iIndex = -1;
			if (NULL != pClassInfo->getType())
			{
				iIndex = GC.getInfoTypeForString(pClassInfo->getType(), true);
			}

			aInfos[iIndex]->copyNonDefaultsReadPass2(pClassInfo);
			[B]SAFE_DELETE(pClassInfo);[/B]

		} while (gDLL->getXMLIFace()->NextSibling(m_pFXml));
	}
}
Bolded line is the error, near the bottom.
So my NonCopyDefaultsReadPass2 is incorrect. What did I do wrong there?
 
You need to change:
Code:
LoadGlobalClassInfo(GC.getSpecialBuildingInfo(), "CIV4SpecialBuildingInfos", "Buildings", "Civ4SpecialBuildingInfos/SpecialBuildingInfos/SpecialBuildingInfo", false);
To be like:

Code:
LoadGlobalClassInfo(GC.getTerrainInfo(), "CIV4TerrainInfos", "Terrain", "Civ4TerrainInfos/TerrainInfos/TerrainInfo", true);
Turns out Units didn't use a readpass2 in native BtS code, because upgrades are through UnitCLASS. Forgot that detail.

So it is even easier than I thought it would be. Just go to that line and change the final false to a true.

Also, why am I changing special Building Info? I added the read pass2 to CvBuildingInfo, not Special Building Info... Should I be changing this line?

Code:
LoadGlobalClassInfo(GC.getBuildingInfo(), "CIV4BuildingInfos", "Buildings", "Civ4BuildingInfos/BuildingInfos/BuildingInfo", false, &CvDLLUtilityIFaceBase::createBuildingInfoCacheObject);
 
I bet xienwolf meant the BuildingInfo and not SpecialBuildingInfo. Try it and see!

I did. I just wondered why he said Special Building Info. When I changed the Building Info LoadGlobalClassInfo parameter to "true" I got the same error message as on the OP.
 
Oops, wasn't paying close enough of attention, I had just done a search for BuildingInfo and copied the first line it gave me!


In your post you called the new function "NonCopyDefaultsReadPass2" which is not the proper name. So potentially that is your issue. You posted a CNDrp2 earlier though, and it was correct in there. Did you declare it in your CvInfos.h? Not sure if a virtual will yell at you for doing that wrong or not.

I really have problems seeing bold on the forum for some reason. Was the error line:

aInfos[iIndex]->copyNonDefaultsReadPass2(pClassInfo);
 
Back
Top Bottom