3

As part of a game engine I am writing in cython, one of the challenges I am facing is creating a consistent interface for error handling that works at the C and python levels of my code. I am interested in maintaining two interfaces to the engine; one that operates at the C-level on structs with no involvement of the python interpreter, and one that operates on python "cdef classes" that indirectly wrap these structs.

Given that raising python Error objects would invoke the GIL, I am interested in using error codes within my C-level functions. These error codes would then be mapped to a corresponding error message string for display on the python wrapper function, which can freely use the GIL.

At the moment, I am planning to store the error codes in a single massive enum alongside a corresponding dict mapping error codes to error strings. This raises the following questions for me:

  1. Should error codes be named per-struct? For example, I have multiple structs that represent common data structures such as vector and slot map. As these are dynamically sized containers, allocations could fail on growing these structures. Would it be better to use generic error codes like ERROR_OUT_OF_MEMORY to represent such failures, or use struct-specific codes like ERROR_ITEM_VECTOR_OUT_OF_MEMORY, ERROR_ITEM_SLOT_MAP_OUT_OF_MEMORY, etc? Another example would be individual graphical shader versus shader program compilation, where I could either use a single ERROR_COMPILE code or an ERROR_SHADER_COMPILE and ERROR_PROGRAM_COMPILE codes.

  2. How should the error code <-> error message mapping be stored? On one hand, I could have a global dict in a header file that maps a unique code to a unique corresponding error message in a centralized location. Alternatively, I could write the strings directly in the python code, which might allow some added flexibility (e.g. using format strings to provide context-specific error messages).

Any help would be appreciated.

4

Should error codes be named per-struct

The granularity of error codes should fit to the existing requirements of the system. Make them as granular as needed to display meaningful error messages, or to detect certain types of failures required for debugging, but not more.

If you don't know how granular you need them yet, start with the simpler approach first, which requires the least effort to implement. I guess in your example this is ERROR_OUT_OF_MEMORY with no separation between structs. When you later get a requirement where it turns out this is not sufficient and you need a finer categorization for "out of memory" errors, refactor immediately. I would not be astonished when you find out you need a completely different categorization, not by "structs" - so any upfront categorization with wrong guessing about the real requirements would have been counterproductive.

As a general principle: try to defer design decisions up to the point in time when you have actual requirements and can foresee their consequences.

How should the error code <-> error message mapping be stored?

Same principle applies here: I guess the global dictionary is the most-simple-to-implement approach and sufficient for now? Then start with that. If it later turns out you need something more flexible, refactor. Note in this case, you still may use the dictionary for 90% of all cases, and hardcoded strings with special parameter formatting for the remaining cases, these approaches are not mutual exclusive.

You may have the problem that you are currently working at a stage where your game engine exists only in "thin air", without a real application using it. Therefore you face the problem of missing requirements. I think the only sensible way of dealing with this problem is to design "test cases" in form of one or more games using your game engine. That will give you the perspective of a user of the engine and help you to find out what is really needed.

  • Yes, I definitely agree, I am overthinking requirements without having any concrete test apps/games! Combining broader error codes and the global dictionary of error messages, I could use error messages like "{class}: out of memory" and have the python caller fill in the details. Of course, if I use multiple c functions in a row before calling back to python, the wrong class name could show up there rather than the class where the allocation initially failed! Error codes aren't like stack traces after all. Or do you mean just using broad error messages as well? – CodeSurgeon Sep 15 at 14:53
  • @CodeSurgeon: honestly, I cannot tell you what kind of parametrization your error codes and messages will require. But if you really have no test games now, as I suspected, I heavily recommend to start with that, so you can find out what makes most sense in the context of your system. – Doc Brown Sep 15 at 15:35
  • Honestly, I think the exact opposite. Make your error codes as specific as possible without becoming unwieldy. There is nothing worse that an error code that just returns "Invalid" as the full error. – IT Alex Sep 16 at 12:40
  • @ITAlex: you missed my point. Surely the OP should design error codes as specific as needed, with a clear vision about costs and benefit. But my point is, I think it does not make sense to overdesign the error code mechanisms in a system in the absence of knowledge of what is really required. So better keep things simple first. – Doc Brown Sep 16 at 12:50
  • @DocBrown Exactly what I disagree with. When it comes to errors I would rather overdesign than underdesign especially with some requirements potentially absent. – IT Alex Sep 16 at 16:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.