A better way of protecting our pointers

Firstly thanks to @Ghyslain_C_Maheux for starting this discussion on Facebook.

So far we have been protecting pointers like this or similar…

if (!(LeftTrack && RightTrack)){ return; };

What this means is if we’re missing a pointer execution continues. However we’ll lose some functionality, and as the projects get bigger we may not notice. It would be nice to have a system that meets my criteria…

  • Execution continues (no lost-work causing hard crashes)
  • We get a helpful warning in the logs, ideally with a line number
  • We can disable the assertions for production code if we want to

A solution even @DanM and @sampattuzzi may approve of is to simply enter the ensure keyword as follows…

if (!ensure(LeftTrack && RightTrack)){ return; };

The sort of yellow warning error you get on the Console in the editor is…

LogOutputDevice:Warning: === Handled error: ===
Ensure condition failed: LeftTrack && RightTrack [File:C:\Users\Ben\Documents\repos\Unreal\04_BattleTank\BattleTank\Source\BattleTank\Private\TankMovementComponent.cpp] [Line: 36]

Neat hey? You can read more about it here at the bottom.

You guys like this? I’m happy to make a lecture on it.

3 Likes

I was looking at ensure last week actually. Was thinking to maybe use check for the init functions and then use ensure on things like your example.

1 Like

I don’t like how check halts execution losing unsaved work though.

Well that is a concern, but so is undefined behaviour from trying to access members on a nullptr.

I’ll start with the solution above, it’s a lot better than what we have for just a few more characters of code.

Probably worth a mention saying it’s probably best to not continue execution if it breaks on one of those inits.

I agree!

I actually like to see execution continue, and performance gracefully degrade. It’s more robust, and if our architecture is right our code should handle it. For example our tank does stop driving or aiming if the movement or aiming component respectively aren’t properly set.

The logs are very obvious, and this sort of error should never get to production code so I think we’re good.

I’m so grateful to you guys for your feedback, in particular @DanM for your consistent and quality help.

Hi Ben!

I’m Ghyslain’s roommate and have been observing the BattleTank project with great interest – I really like the way you teach, it is very compelling and accessible – awesome job on that!!

Having worked in the AAA video game industry for close to 10 years (Ubisoft, Eidos) as a Level Designer and then Technical Director, I had brought up the error handling of this project (or lack thereof, in the case of skipping over a function completely if the conditoins are not met, without any warning/error log) during one of our discussions here at home, and would like to add my thoughts to this thread.

Seeing how Ensure does provide a Log Warning when conditions aren’t met (with the actual .cpp file and line #) is very nice, however I do feel it is lacking in the information it provides to the end-user (read here: developer who is not a programmer on the project). As a developer than handles data (and not code) on a daily basis, having asserts with a proper description of WHY we are encountering said assert is incredibly useful and time-saving.

Let’s say for instance we have an object in which we pass an invalid value to one of its properties. When birthing this object in our scene, we could be getting an assert saying something like this, and then continue the execution (with, albeit, loss of some functionality, or as you put it, gradual performance degradation).

Invalid value for property in object in map .

This is a BASIC assert that I still find lacking in the amount of information it provides to the end user. A better assert could be something like the following:

Invalid value for property in object in map . Value needs to be between [lower limit] and [upper limit]

(you can imagine this assert being handled when an int/float value is being set by the developer but outside of what the code expects).

Yes, it does imply more work for the programmer and more lines of code, but this information saves so much time in the sense that:

  • The end user actually has a real idea of why the function does not work and what data is causing the error.
  • The end user does not simply have a “freeze/crash”. If the error is not properly handled, which is not the case for your example where the function is simply skipped if the pointer is null – he actually has information provided to him.

This also saves a lot of time to the programmer in the scope of a AAA project. Bigger games can have many millions of lines of code, and when you create a function using other parts of the code (which you are not the author of), having proper asserts can save you major headaches when debugging why your own code isn’t working :slight_smile:

All in all, I understand that your course and the BattleTank project both have limited scopes and are generally aimed at programmers, but I do feel that proper error handling (and highlighting the importance of it) do have a place in your videos since they are aimed at giving formation to people that actively want to join the video game industry, where asserts (with proper descriptions!) are used profusely to provide information to other departments, whether it be Level Design, Level Art, Animation, Visual FX or Sound.

Thank you, Ben, for being so active for your students, and for taking the time to provide insightful replies.

David

TL;DR

  • Asserts and error handling (plus providing information to the end-user) are, in my opinion, a critical part of any major game development project.
  • They help both the programmers AND the team work more efficiently, which is a critical part of making games (the longer you take, the more money it costs, etc.).
  • The feedback/output/log message needs to be written in a way that the end-user can understand – think plain English for someone that has no clue what a variable or a function is, and only handles data.
  • Execution, as you said, can/must continue. Xbox devkits have the assert handling done right, where you can actually choose between “Ignore”, “Ignore All” and “Dump” when you encounter an assert. Ignore will simply ignore this instance of said assert, “Ignore All” will remove all future instance of said assert, and “Dump” will dump a crash log / call stack.
4 Likes

Thank you so much for this. I have just published the first video on error handling, hope you like it.

Hopefully you’re happy with the final treatment in the section, and my reasons for it.

The best way to be protected by memory leaks is not to use pointers.

There are two alternatives

a) Use Unreal’s own garbage collector
b) Use smart pointers

Unreal garbage collector will handle memory for you but you will have to use Unreal objects. If for some reason that solution does not meet your needs the alternative are smart pointers. Smart pointers work the same as normal pointers (also called raw pointers) the difference is that they release themselves when they get out of scope so you wont have to do this manually. Smart pointers can be used in any case that a raw pointer is expected.

On the subject of Assert, assert is good as introduction to beginners because it shows the value of automated testing and it can work well for few test, but when the test become many or when you dont want the test to be part of your main code then its time to move to a proper unit testing framework. It will make the whole process, easier and more flexible.

My style of coding is to avoid as much as I can if conditions and assets, instead I use method switching and unit testing. The more you check things, the more complicated and ugly your code becomes.

In case you wondering method switching is a Smalltalk technique (the language that gave us class based object orientated programming) which basically mean that instead of checking (with and if statement or switch statement) whether data is of specific type instead you use a simple method call and the branch of execution will depend on the type of object used for the method call. This way you can have objects having methods of the same name but each method is doing thing differently depending on the object. One of the big advantages of OOP.

1 Like

Non-owning raw pointers are fine. Owning raw pointers are not.

Hello, I have added ensure keyword about everywhere but I don’t get any warning in log. Is there some parameter to switch on or an include to add ?

Privacy & Terms