[Python/C++]Add text for C++ generated python exception

phungus420

Deity
Joined
Mar 1, 2003
Messages
6,296
I've been debugging my dll over the past week. Caught many causes of Assert failures. Many of these were being caused by python. I would not have been able to figure out what was wrong with most of these without the help of Emperor Fool who showed me how to get Python to throw exceptions, this is very useful:
Code:
CyClass::Function(){
if (...Bad thing you want to catch python doing)
{
       throw new exception();
}
}
Many of these "asserts" (or control statements to force python exceptions) I'd really like to leave in because they are actually catching things that are going wrong. In most instances the Cy function gets it's information from python then passes it to the Cv parent function in the dll, and the dll immediately throws an assert failure. There is no point in not catching the problem in python where it is going wrong. The exception being thrown gives the line number causing it, allowing me to track the bug, but unfortunately it does not tell me anything about the error; all it says is "Unknown C++ exception". For right now that's fine when I'm tracking a specific bug and I know what function is calling it, because I just wrote the control statement that causes the exception to be thrown. However if I leave these exception statements in the sdk, that wol't be the case a couple months from now, and I might end up with seemingly random asserts that I am unable to debug.

I tried looking up information on exceptions, but couldn't find much. http://www.cplusplus.com had some decent information, but it's all written for coding done specifically for the console (uses cout << "message"; as it's examples). And it doesn't really describe how to pass a message to python. I've also tried looking through the python documentation, and posting on a python internet discussion board, but no dice. I probably just don't know enough about coding to search for the correct terms.

Basically what I want to know is, is there a way I could do something like this:

Code:
CyClass::Function(){
if (...Bad thing you want to catch python doing)
{
   throw new exception();
   //Get python to print something in it's exception message so I know what is the problem in the future

   //Have python resume as if the exception wasn't thrown, so that this does not effect final_release and No_python exception user oriented builds (this is only for developers)
}
}
And if so how?
 
I'm not quite sure exactly what you want to do. In the sdk code you were probably debugging, you will see a lot of:
Code:
FAssertMsg((GC.getNumTraitInfos() > 0), "GC.getNumTraitInfos() is less than or equal to zero but is expected to be larger than zero in CvPlayer::init");
Is it clear to you how this works? That code is conditionally compiled; in a debug version, it is there, in a final release version, the code is not there.

Is that the type of effect you are looking for? Unfortunately python is not compiled, so there is no way to achieve this exact effect. The use of "assert" causes philosophical arguments among professional programmers, but let me ask, is there a reason you would *not* want this message printed during a normal player's game? If the bad thing is happening, don't you want them to know? If so, then you do not care about conditional compilation. You want a message which always appears.

It may be that what you are trying to achieve is to just continue processing even when an underlying C++ routine throws an exception on its own. If so, the following code will do this. I use it all the time. It prevents the game from stopping due to this error; but it really isn't clear why it happens or what to do about it. So this may not be a perfect solution.
Code:
	# Entry point for drake AI, called from entrypoints/gameinterface.py
	def DrakeAI(self, argsList):
		pUnit = argsList[0]
		iX = pUnit.getX() ; iY = pUnit.getY()
		[...]
		try: # occasionally throws unrecognized C++ exception
			pGroup = pUnit.getGroup()
			pGroup.clearMissionQueue()
			pGroup.pushMoveToMission(iX, iY)
			pUnit.finishMoves()
			return true
		except:
			pass
		return false
This does some work and then calls pGroup.pushMoveToMission, in order to make the unit move to that location. I observe that sometimes that call generates an exception, but I don't know why. So I have "wrapped" it in an exception catcher. The lines after "try" are executed. If any of them cause an exception for any reason, that segment of code stops executing. Nothing I can do about that. But instead of printing a failed message to the user, the python interpreter resumes execution at the "except" line. This says "pass" which means do nothing, and the code continues on to return false.

This may not do exactly what you want. But, maybe this example will enable you to find some keywords to do more searching in the python user community. Or, post a followup question with a little more detail, and we can probably give you a more exact answer.
 
I'm not sure how I can be more clear. I'll try to restate.

In a Cy source file (used to pass information to and from the dll and python), I can add in a control statement that will cause python to throw an exception. This exception is written in the C++ source, but the exception occurs in the python.

For instance there was an assert I was tracking where there was a function in CvPlayer that checked the attitude of a player; this function had an assert that the two players passed to it (iPlayer and iSomethingPlayer) should not be the same player, as the player shouldn't be evaluationg what it thought of itself. I tracked the assert to CyPlayer, where it was "lost in the python". In order to find the offending code in python that was causing CyPlayer to pass the same players to CvPlayer's function, I added:

if(iPlayer == iSomethingPlayer)
{
throw new exception();
}

which caused python to immediately throw an exception if this function was called in python, and this condition was true (iPlayer == iSomethingPlayer). Now if this statement is true when this function in CyPlayer is called by python, then CvPlayer is going to immediatly throw an assert when CyPlayer passes it on to the dll; so it seems reasonable to leave this piece of debugging code in there. However the exception python throws is just "Unidentifiable C++ Exception", and give the line number and file that is causing the exception.

What I want to know is, is it possible to have the throw exception code in a Cy function create a message more descriptive in Python's exception message; so that when debugging in the future it will be possible to figure out what is causing the exception. Also, and arguably more important is, is it possible to have python resume normally after the exception is thrown, so that the rest of what python was doing doesn't break for users who don't want to know and shouldn't know that some minor issue has just happened.
 
So, you want it to print a message, and then go on. How about "CyInterface().addMessage" or even "print"? Why is an exception needed? I am not an expert in python, but it seems "throw" is an older name and the current recommended name is "raise". The point of "raise" is to exit the program. If you don't want to exit, then you shouldn't use exceptions. It is possible to use "raise" in a way which doesn't exit; but that means you need to add an exception *handler* which seems like two extra steps. I would look to using a simple print statement of some kind if you want the program to continue along after giving the information.
 
I suppose that would work, yes. As an example, assume I have function CyPlayer::Foo(iValue1, iValue2) and I want it to print to the python error logs (seems the best place for this info), if iValue1 == iValue2 the file name and the line number where this condition is occuring? Keep in mind I need to code this in the dll, as finding where this is happening in python is the whole point.
 
Sorry, I had misread the entire thread. You want to put this into the sdk code, not the python code. So my python examples were not helpful.

I do not use any fancy logging inside the sdk, I just use plain old "fprintf". But, it may be helpful for you to search on "messageControlLog" which appears to be one of the main logging functions.
 
I do not use any fancy logging inside the sdk, I just use plain old "fprintf". But, it may be helpful for you to search on "messageControlLog" which appears to be one of the main logging functions.
Definately do not want to use a general printing message, this is only for development, and not meant to bother most users. I'll take a look at messageControlLog, thanks, hopefully I'll be able to figure something out from there.

What is messageControlLog, is this a function in the SDK?
 
What is messageControlLog, is this a function in the SDK?

Yes, please search for it in the sdk files to see examples. It seems this is used to produce MPLog.txt in the my games/beyond the sword/logs directory. I know there are a bunch of different log files, and I am sure different functions print to different logs. But, I don't use them so I don't know much more.
 
The whole problem is that phungus wants to know the file and line # in Python that called out to the DLL. Writing an "it broke" message from C++ won't be helpful since the function could have been called from other DLL code or any place in Python. This all started with Civ4lerts calling CyTeam::isHasMet(TeamTypes) with NO_TEAM as the argument, but this function is called from many places in BUG, BTS's Python, and the DLL.

One correction here, "throw new exception()" makes C++ throw an exception. This exception travels to the Boost::Python layer which translates it into a Python exception. The problem is that it causes the call chain in the DLL to unroll so you cannot keep going.

You could expose a method in CvAppInterface that the DLL could call to print a message, but again this isn't useful without knowing what to print. You need to find a way to query the Boost::Python library for the most recent Python file and line # that made the call. Then you could dump that into bull.log.
 
You are all thinking too complicated.
If a cause of an Asserst failure is suspected to be in python, all you need to do is change the exposing function a bit.
Code:
bool CyTeam::isHasMet(int /*TeamTypes*/ eIndex)
{
	[COLOR="Green"]//Fuyu: Catching Civ4lerts mess-ups[/COLOR]
[B]	FAssertMsg(eIndex >= 0, "eIndex is expected to be non-negative (invalid Index) (Python)");
	FAssertMsg(eIndex < MAX_TEAMS, "eIndex is expected to be within maximum bounds (invalid Index) (Python)");
	if (eIndex < 0 || eIndex >= MAX_TEAMS)
	{
#ifdef _DEBUG
		throw new exception();
#endif
        return false;
	}[/B]

	return m_pTeam ? m_pTeam->isHasMet((TeamTypes)eIndex) : false;
}
With that, you will 1) get the information about what is wrong, plus 2) the confirmation that the cause is indeed in the python code from the Assert message, then you press continue[/I] Ignore Once, and because of the exception thrown the python error logs will tell you 3) which calls were leading to that failure.
It's not as great as python errors that already tell you what is wrong, but it's simple and should work just as well.
Right?

This all started with Civ4lerts calling CyTeam::isHasMet(TeamTypes) with NO_TEAM as the argument
Btw I'm still getting that Assert failure from Civ4lerts, I believe it's line 941
Code:
				if eActiveTeam != eNewEnemy and not activeTeam.isHasMet([U]eNewEnemy[/U]):
Unlike eTeam, eNewEnemy can be -1 there.


edit: works as expected.
[PythonDbg.log]
20:12:28 DEBUG: BugEventManager - event BeginActivePlayerTurn: (0, 19)
20:12:33 TRACE: Error in BeginActivePlayerTurn event handler <bound method WorstEnemy.onBeginActivePlayerTurn of <Civ4lerts.WorstEnemy instance at 0x0CE7ECD8>>
20:12:33 TRACE: unidentifiable C++ exception


[PythonErr.log]
Traceback (most recent call last):
File "BugEventManager", line 361, in _handleDefaultEvent
File "Civ4lerts", line 895, in onBeginActivePlayerTurn
File "Civ4lerts", line 941, in check
RuntimeError: unidentifiable C++ exception
 
Back
Top Bottom