• We have updated our Community Code of Conduct. Please read through the new rules for the forum that are an integral part of Paradox Interactive’s User Agreement.

Kaelic

Major
112 Badges
Feb 28, 2004
705
222
  • Sword of the Stars
  • Europa Universalis III Complete
  • King Arthur II
  • Leviathan: Warships
  • Magicka
  • March of the Eagles
  • Europa Universalis III Complete
  • Naval War: Arctic Circle
  • Europa Universalis IV: Res Publica
  • Victoria: Revolutions
  • Europa Universalis: Rome
  • Semper Fi
  • Sengoku
  • Heir to the Throne
  • Sword of the Stars II
  • The Showdown Effect
  • Victoria 2
  • Victoria 2: A House Divided
  • Victoria 2: Heart of Darkness
  • Rome: Vae Victis
  • Warlock: Master of the Arcane
  • 500k Club
  • Cities: Skylines
  • Europa Universalis IV: El Dorado
  • Europa Universalis IV: Pre-order
  • Hearts of Iron IV: No Step Back
  • Darkest Hour
  • Ancient Space
  • Hearts of Iron II: Armageddon
  • Crusader Kings II
  • Crusader Kings II: Charlemagne
  • Crusader Kings II: Legacy of Rome
  • Crusader Kings II: The Old Gods
  • Crusader Kings II: Rajas of India
  • Crusader Kings II: The Republic
  • Crusader Kings II: Sons of Abraham
  • Crusader Kings II: Sunset Invasion
  • Crusader Kings II: Sword of Islam
  • Commander: Conquest of the Americas
  • A Game of Dwarves
  • Deus Vult
  • Europa Universalis III
  • Divine Wind
  • Europa Universalis IV
  • Europa Universalis IV: Art of War
  • Europa Universalis IV: Conquest of Paradise
  • Europa Universalis IV: Wealth of Nations
  • Europa Universalis IV: Call to arms event
  • For the Motherland
  • Hearts of Iron III
After 12 hours of playing and not even reaching 1942 I decided to look into why HoI3 runs so slow. One thing I noticed was everytime an hour blocked (froze on the hour before continuing) the message/event panel would spam a bunch of trade info, spy info etc. So it occured to me: the ai is what is eating up all the time making constant decisions about diplomacy and potentially other things.

It's not going to be rendering (obviously since it's fine when paused) and I really doubted combat or pathfinding would be slow (unless they just learned to code a week ago).

Soooo I dug into ./script/ and opened the ai_diplomacy.lua file to see what kind of shit was going on.

:eek:

Single functions were being called to handle so many decisions with so many logical branches of code that it was no wonder speed was a pile of crap, just look at this function for a SINGLE trade offer.

Code:
function DiploScore_OfferTrade(ai, actor, recipient, observer, action)
	if observer == actor then
		return 100 -- handled from foreign minister
	else -- evaluate offer of trade from actor
		--Utils.LUA_DEBUGOUT("TRADE ========")
		--Utils.LUA_DEBUGOUT("actor: " .. tostring( actor ))
		--Utils.LUA_DEBUGOUT("recipient: " .. tostring( recipient ))
		local score = 0
		local actorCountry = actor:GetCountry()
		local recipientCountry = recipient:GetCountry()
		local route = action:GetRoute()
		
		if ai:AlreadyTradingResourceOtherWay( action:GetRoute() ) then
			return 0
		end
		
		--Utils.LUA_DEBUGOUT("from: " .. tostring( route:GetFrom() ))
		--Utils.LUA_DEBUGOUT("to: " .. tostring( route:GetTo() ))
		
		local MAX_GOODS = CGoodsPool._GC_NUMOF_-1
		for goods = 0, MAX_GOODS do
			local balance = recipientCountry:GetDailyBalance(goods):Get()
			if goods == CGoodsPool._SUPPLIES_ then
				balance = math.min( balance, recipientCountry:GetSupplyBalanceAverage():Get() )
			end
			
			local absBalance = math.abs(balance)
			local inTrade = route:GetTradedFromOf(goods):Get()
			local outTrade = route:GetTradedToOf(goods):Get()
			if absBalance > 0.005 and (outTrade > 0.01 or inTrade > 0.01) then
				--Utils.LUA_DEBUGOUT("____________goods : " .. GOODS_TO_STRING[goods] )
				--Utils.LUA_DEBUGOUT("balance : " .. balance )
				
				if inTrade > 0.001 and absBalance > 0.001 then
					--Utils.LUA_DEBUGOUT("GetTradedToOf: " .. inTrade)
					local tradeFactor = 0.0
					if balance < 0.0 then
						tradeFactor = math.min(inTrade / (-balance) * 2, 1.0)
						score = score + math.min(tradeFactor, 1.0) * 100
						--Utils.LUA_DEBUGOUT("tradefactor: " .. tradeFactor)
						--Utils.LUA_DEBUGOUT("inTrade: " .. inTrade)
						--Utils.LUA_DEBUGOUT("balance: (-)" .. balance)
					else
						if not (goods == CGoodsPool._MONEY_) then
							tradeFactor = math.min(inTrade / balance, 1.0) * 0.5
							score = score - math.min(tradeFactor * 2, 1.0) * 100
							--Utils.LUA_DEBUGOUT("tradefactor: " .. tradeFactor)
							--Utils.LUA_DEBUGOUT("inTrade: " .. inTrade)
							--Utils.LUA_DEBUGOUT("balance: ()" .. balance)
						end
					end
					
					--Utils.LUA_DEBUGOUT("tradefactor: " .. tradeFactor)
					--Utils.LUA_DEBUGOUT("score: " .. score)
				end
				
				if outTrade > 0.001 then
					--Utils.LUA_DEBUGOUT("GetTradedFromOf : " .. outTrade )
					--local tradeFactor = 0
					--if (balance > 0) then
					--	tradeFactor = (absBalance - outTrade) / absBalance
					--else
					--	score = 0
					--end
					--score = score + tradeFactor * 100
					
					if balance > 0.0 then
						tradeFactor = math.min(outTrade / (balance), 1.0)
						if tradeFactor > 0.95 then
							if tradeFactor > 0.999999 then
								tradeFactor = 0
							else
								tradeFactor = (0.06 - tradeFactor - 0.95)
							end	
							score = score + math.min(tradeFactor, 1.0) * 100
						elseif tradeFactor > 0.7 then
							score = score + 85
						elseif tradeFactor > 0.3 then
							score = score + 75
						elseif tradeFactor > 0.001 then
							score = score + 60
						end
						--score = score + math.min(tradeFactor, 1.0) * 100
						--Utils.LUA_DEBUGOUT("tradefactorz: " .. tradeFactor)
						--Utils.LUA_DEBUGOUT("outTrade: " .. outTrade)
						--Utils.LUA_DEBUGOUT("balance: (-)" .. balance)
					else
						--tradeFactor = math.min(outTrade / balance, 1.0) * 0.5
						score = score - 1000 -- math.min(tradeFactor * 2, 1.0) * 100
						--Utils.LUA_DEBUGOUT("tradefactor: " .. tradeFactor)
						--Utils.LUA_DEBUGOUT("outTrade: " .. outTrade)
						--Utils.LUA_DEBUGOUT("balance: (-)" .. balance)
					end
					
					
					--Utils.LUA_DEBUGOUT("tradefactor: " .. tradeFactor)
					--Utils.LUA_DEBUGOUT("score: " .. score)
				end

				--Utils.LUA_DEBUGOUT("____________")
			end
		end
		
		-- we need transports to trade
		if actorCountry:NeedConvoyToTradeWith( recipient ) then
			if route:GetConvoyResponsible() == recipient then
				if recipientCountry:GetTransports() == 0 then
					score = 0
				end
			end
		end
		
		--Utils.LUA_DEBUGOUT("score before strategy" .. score)
		
		if score > 0.001 or score < -0.001 then
					
			if score > 30 then			
				local rel = ai:GetRelation(recipient, actor)
				local strategy = recipient:GetCountry():GetStrategy()
				score = score - strategy:GetAntagonism(actor) / 15				
				score = score + strategy:GetFriendliness(actor) / 15
				score = score - rel:GetThreat():Get() / 2
				if rel:IsGuaranteed() then
					score = score + 5
				end
				if rel:HasFriendlyAgreement() then
					score = score + 10
				end
				if rel:AllowDebts() then
					score = score + 5
				end
				if rel:IsFightingWarTogether() then
					score = score + 15
				end
				
				--Utils.LUA_DEBUGOUT("GetAntagonism" .. tostring(strategy:GetAntagonism(actor)))
				--Utils.LUA_DEBUGOUT("GetFriendliness" .. tostring(strategy:GetFriendliness(actor)))
				--Utils.LUA_DEBUGOUT("GetThreat" .. tostring(rel:GetThreat():Get()))
				--Utils.LUA_DEBUGOUT("score after country stuff" .. score)
				local relation = rel:GetValue():GetTruncated()
				score = score + relation / 5
			

			end
			--Utils.LUA_DEBUGOUT("score after country stuff" .. score)
		end
		--Utils.LUA_DEBUGOUT("===================\n")
		return Utils.CallScoredCountryAI(recipient, 'DiploScore_OfferTrade', score, ai, actor, recipient, observer)
		
	end
end

This is after a check in the minister AI to see if it would like to propose a trade, it gets worse.

Code:
function ProposeTrades(minister)
	local SMALLEST_TRADE = 0.03
	local ministerTag = minister:GetCountryTag()
	local ministerCountry = minister:GetCountry()
	local ai = minister:GetOwnerAI() 
	local strategy = ministerCountry:GetStrategy()
	
	local bestScore = -10000
	local bestAction = nil
	
	-- TODO: SCALE WITH AMOUNT THEY HAVE IN STOCKPILE FOR "STABILITY"

	-- see what we are low on and find someone who is hoarding it
	local MAX_GOODS = CGoodsPool._GC_NUMOF_-1
	local myMoney = ministerCountry:GetDailyBalance( CGoodsPool._MONEY_ ):Get()
	for goods = 0, MAX_GOODS do
		if not (goods == CGoodsPool._MONEY_) then
			local balance = ministerCountry:GetDailyBalance( goods ):Get()
			if balance < 0.01 and myMoney > 0.01 then
				--Utils.LUA_DEBUGOUT( "I'm low on " .. goods .. tostring(ministerTag) )
				for country in CCurrentGameState.GetCountries() do
					local countryTag = country:GetCountryTag()

					if country:Exists() and countryTag:IsReal() and
					not (ministerCountry:HasDiplomatEnroute(countryTag)) and
					not (countryTag == ministerTag) then
					
						local rel = ministerCountry:GetRelation( countryTag )
				
						local theirBalance = country:GetDailyBalance(goods):Get() * 0.6
						if goods == CGoodsPool._SUPPLIES_ then
							theirBalance = math.min( theirBalance, country:GetSupplyBalanceAverage():Get() * 0.6 )
						end
						
						if theirBalance > 0.01 then
							local requested = math.min( theirBalance, -balance )
							local action = CTradeAction( ministerTag, countryTag )

							if action:IsValid() and action:IsSelectable() then
								action:SetTrading( CFixedPoint(requested), goods )
								local money = action:GetTrading( CGoodsPool._MONEY_, ministerTag ):Get()
								
								local factor = 1.0
								if money > myMoney then
									factor = myMoney / money - 0.08
									
									if factor > 0.01 then
										local action2 = CTradeAction( ministerTag, countryTag )
										local amount = requested * factor
										if amount > SMALLEST_TRADE then -- remove small stuff
											action2:SetTrading( CFixedPoint(amount), goods )
											
											local score = 70 --balance
											score = score - rel:GetThreat():Get() * CalculateAlignmentFactor(ai, ministerCountry, country)
											score = score + (100 * amount / math.abs(balance))
											score = score + math.floor(theirBalance / 10) -- big producer bonus = more stable
											
											-- we need transports to trade
											if ministerCountry:NeedConvoyToTradeWith( countryTag ) then
												if action2:GetRoute():GetConvoyResponsible() == ministerTag then
													if ministerCountry:GetTransports() == 0 then
														score = 0
													end
												else
													if countryTag:GetCountry():IsAtWar() then
														score = score - 20
													end
												end
											end
											
											local acceptanceChance = action2:GetAIAcceptance()
											acceptanceChance = acceptanceChance - minister:GetSpamPenalty(countryTag)
											if ( factor > 0.01 and acceptanceChance > 50 ) then
												if score > bestScore and action2:IsConvoyPossible() 
												and (not ai:AlreadyTradingResourceOtherWay( action2:GetRoute() ) )
												then
													bestAction = action2
													bestScore = score
												end
											end
										end -- if amount
									end
								elseif requested > SMALLEST_TRADE then
									local score = 70 --balance
									
									score = score - rel:GetThreat():Get() * CalculateAlignmentFactor(ai, ministerCountry, country)
									score = score + (100 * requested / math.abs(balance))
									score = score + math.floor(theirBalance / 10) -- big producer bonus = more stable
									
									-- we need transports to trade
									if ministerCountry:NeedConvoyToTradeWith( countryTag ) then
										if action:GetRoute():GetConvoyResponsible() == ministerTag then
											if ministerCountry:GetTransports() == 0 then
												score = 0
											end
										else
											if countryTag:GetCountry():IsAtWar() then
												score = score - 20
											end
										end
									end
										
									local acceptanceChance = action:GetAIAcceptance()
									acceptanceChance = acceptanceChance - minister:GetSpamPenalty(countryTag)
									if ( factor > 0.01 and acceptanceChance > 50 ) then
										if score > bestScore and action:IsConvoyPossible()
										and (not ai:AlreadyTradingResourceOtherWay( action:GetRoute() ) )
										then
											bestAction = action
											bestScore = score
										end
									end
								end
							end
						end
					end	
				end
			end
		end	
	end
	
	if bestAction then
		ai:PostAction( bestAction )
	end
	
end

Lua is not fast when used in this way and especially when functions like these are being called nearly 200 times EVERY IN GAME HOUR (or every couple hours or per day, depends on how often they do it, you can tell when the hours stall). Potentially they're not even staggering the AI updates and simply doing it all at once, as opposed to spreading it across the month doing a few every day/hour.

Won't take my word for it? Edit the lua files to all return 0 so that no trade or spy or diplomacy logic is performed. The game runs through days at lightning speed.

Why don't I think this can be easily solved? Because they'd have to re-write this AI logic in C/C++ where it wouldn't even stumble to process this but as a result you'd lose the cool modable AI potential. Other alternatives include staggering the updates across different frames, but they may already be doing this. Lastly they could potentially write a system to process lua in chunks on threads then update lots of lua states at once; however this would require a significant change in the way the AI works and interfaces back with C in order to make it thread safe and work in a data processing In-Out way.

Sorry to ramble on but just wanted everyone to know my perspective on the performance problem they have. I don't really blame them here because it's clear what they wanted it just has drawbacks and will require some serious thinking to get around it. Hopefully people will understand this and not just mindlessly claim "IT'S A FLOP! HOW COULD THEY HAVE DONE SOMETHING THIS STUPID?".
 
Interesting study.

But couldnt we get a large performance boost if they just made trade checks once or twice per day instead of every hour?

Would this disrupt the AI?
 
Obviously it would, we're thinking and deciding in real-time after all. Restricting the AI to reduce its decision-making process would certainly decrease its potential.

I'll rather have better AI than lightning speed that I only use during peace-time in a war game anyway. No need to dumb down an AI that is more up to the challenge.
 
mmmm, maybe you hould have coded it for them :D
You seem to know what you're doing.

Wouldnt it be interesting to hear what a dev has to say about this ?
 
Interesting study.

But couldnt we get a large performance boost if they just made trade checks once or twice per day instead of every hour?

Would this disrupt the AI?

That would be an idea, say makes one attempt every day at noon. This would also be easy to accept realism wise as every day diplomacy wasn't going on at 2 am. It happened during business hours.
 
Interesting find
I wonder if it is easy enough for the modder community to simply change the frequency of this check to provide a quick workaround till new patch adress the issue
 
I suspect its the waether hoi is calculating every hour for every province which causes a high procentage of lag. do it one or two time per day would be realitic enough for me. But dont know how many resources this is realy eating.
 
I suspect its the waether hoi is calculating every hour for every province which causes a high procentage of lag. do it one or two time per day would be realitic enough for me. But dont know how many resources this is realy eating.

Weather map mode is my fastest running, strangely. Even more then Terrian...
 
Good job Kaelic. I think I would prefer having all diplomacy decisions 2 or 3 times a day. Of course it would be a little abstract. If it's say 8am, 1pm and 5pm GMT then in Australia the diplomats would have to work at night. I think thats a small price to pay for smoother passage of time.

I hope Paradox notice this thread and consider alternatives.

EDIT: Do you have to time/patience to try and mod in a quick fix for us? Just change the frequency. If it worked you would get a lot of thanks and I suspect it would also help PI balance the situation.