Advertisement · 728 × 90

Posts by Checkpoint Earth

It's 3:14pm EDT! Happy Pi Day again!

#PiDay

1 month ago 0 0 0 0
Preview
The Infinite McConaughey Live Stream YouTube video by Craven In Outer Space

Celebrate #PiDay with the Infinite McConaughey Livestream event. Starts today at 3:14 PM ET.

1 month ago 8 1 0 1
Video

It's π Day! To celebrate this enigma, who is also the player character in our game, here's a gameplay video that demonstrates some of the sounds crafted by @craveninouterspace.bsky.social Watch it unmuted. If you're wondering what's going on, you'll enjoy the game!

#PiDay #IndieGames #GameDev

1 month ago 5 3 0 1

That voice was telling me that people buy all sorts of things and to keep working. So that's what I'm doing today (no, my game does not contain sex)

1 month ago 0 0 0 0

On my vision quest, the one that led me to the game I'm working on today, the question that kept coming up was: "why would anyone buy this game?" The immediate and crude response I got was: "people pay to watch other people having sex". (it used a different word for "having sex")

1 month ago 0 0 1 0
Video

Here's a sneak preview of one of the challenges in AGENCY. If you're wondering what's going on, you'll have a great time in the game! Shout out to @craveninouterspace.bsky.social for the idea and the music! (Watch it with your audio turned on.)

#indiegames #indiedev

1 month ago 3 1 0 0
Video

So I wrote that thread on hunting a bug in our game. I was initially thinking of writing a blogpost, maybe even make a video. but then this happened lol

#indiegames #indiedev #indiegamedev #tuesdaynegotiations

1 month ago 4 2 0 0

I now have to go through my entire project and make sure the pointers are all defined and used properly. Save me, ripgrep. You're my only hope!

If you have a correction to make or have questions, please share them!

/end

#Unreal #GameDev #IndieGameDev #BugHunting

1 month ago 4 2 0 0
Advertisement

That said, I am aware that there are other things I don't know. So even though I can no longer reproduce this issue with the fix, I am open to the possibility that I get a crash in the future with similar symptoms. Well, that investigation will happen when it needs to.

23/

1 month ago 0 0 1 0

Sometimes, one has no choice but to implement a known solution to fix a problem and move on to other things. But when it's possible, it's nice to take the time to really understand the bug. It's fun to learn the internals of something.

22/

1 month ago 0 0 1 0
Preview
Object Pointers in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community Unreal Engine provides several templated, smart pointers designed for a variety of use-cases.

So with this fix, "IsValid()" should work properly because the underlying memory will not be re-used until I no longer have access to it myself. I could go one step further here and use TWeakObjectPtr, which will get the GC to perform pointer nullification itself if the object is destroyed.

21/

1 month ago 0 0 1 0
Preview
Garbage Collector Internals | Knowledge base Unreal Engine’s Garbage Collector is a standard Mark & Sweep collector. This post explains its stages and what it does under the hood. Summary These ar...

The solution is to change how the variable is defined:

UPROPERTY()
TObjectPtr<ASomeObject> CurrentInteractable;

UPROPERTY is a C++ macro specific to Unreal. In conjunction with TObjectPtr, it prevents the GC from reusing the memory until I manually set the variable to "nullptr" myself.

20/

1 month ago 0 0 1 0

In the previous screenshot, who knows what that area of memory "CurrentInteractable" points to and what it's being reused for. As far as "IsValid()" is concerned, the "object" is valid but it's just some random area in memory and doing anything on it cannot lead to anything good.

19/

1 month ago 0 0 1 0
A screenshot from the debugger that shows what "CurrentInteractable" looks like in memory the bug happens. The ObjectFlags do not include "RF_MirroredGarbage", so "IsValid()" thinks the object is valid.

A screenshot from the debugger that shows what "CurrentInteractable" looks like in memory the bug happens. The ObjectFlags do not include "RF_MirroredGarbage", so "IsValid()" thinks the object is valid.

I needed to verify this so I added some breakpoints and tried reproducing the bug. Here's a screenshot showing the "CurrentInteractable" variable when the bug happens. The "ObjectFlags" area do not include any garbage flags, so "IsValid()" returns true here.

18/

1 month ago 0 0 1 0

So if the object was destroyed prior to running this code, "CurrentInteractable" becomes a pointer address pointing to _some_ area in memory. "IsValid()" will interpret data in that area in memory but its answers shouldn't be expected to be coherent. Who knows what data it's looking at.

17/

1 month ago 0 0 1 0

Well, it turns out that when the GC cleans up the object, it doesn't necessarily return the memory used by the object back to the OS. The memory allocator might reuse the returned memory for a different object without involving the OS. Of course! That's a classic thing to do for performance.

16/

1 month ago 0 0 1 0

Maybe the object was destroyed prior to running this code. But why would "IsValid(CurrentInteractable)" return true, though? Shouldn't it have seen the "Garbage" flag set on the object and returned false, instead?

15/

1 month ago 0 0 1 0
A screenshot from the debugger that shows what "CurrentInteractable" looks like in memory when the garbage flags are set on it before the object is cleaned up. The ObjectFlags includes "RF_MirroredGarbage", which is what "IsValid()" checks for.

A screenshot from the debugger that shows what "CurrentInteractable" looks like in memory when the garbage flags are set on it before the object is cleaned up. The ObjectFlags includes "RF_MirroredGarbage", which is what "IsValid()" checks for.

Here's a view from the debugger when the object actually has the garbage flags set on it but before the object is cleaned up by the GC. Notice the flag "RF_MirroredGarbage". The "IsValid()" function checks for this. FYI, I censored parts of the screenshot to not spoil the game :)

14/

1 month ago 0 0 1 0
Advertisement

The other possibility was that "IsValid()"'s results are unreliable when used with a raw pointer. But that makes no sense. IsValid()'s implementation is simple: check if the pointer is not a nullptr and check that the object doesn't have a "Garbage" flag set on it.

13/

1 month ago 0 0 1 0

If nothing else can preempt my code to clean the object, and if the garbage collector is not running in parallel to clean the destroyed object, then if "IsValid()" says the object is valid, shouldn't the object remain valid within the if statement block?

12/

1 month ago 0 0 1 0

This had the appearance of a race condition. Something between triggering the event, destroying the actor, and Unreal's garbage collector. But why? I used "IsValid()", didn't I? Unreal's garbage collector (GC) is supposed to be synchronous, at least the part that cleans up an object.

11/

1 month ago 0 0 1 0

This latter declaration is safer if you're referring to objects whose lifecycle is managed by the engine. I should have written it that way to begin with but this is an older piece of code, written when I was less experienced with the engine's internals. But anyway, why did it crash?

10/

1 month ago 0 0 1 0

I should point out that "CurrentInteractable" is an instance variable and it's a raw pointer. It's something like this:

ASomeObject* CurrentInteractable;

In Unreal, it's better to do something like:

UPROPERTY()
TObjectPtr<ASomeObject> CurrentInteractable;

9/

1 month ago 1 0 1 0

The issue is this: despite "IsValid()" returning true, the call to "Execute_EndInteract" fails and the stack trace usually is something weird. It felt like the object was actually a garbage object inside the if statement block. So why did "IsValid()" return true?

8/

1 month ago 0 0 1 0

A quick explanation: "IsValid()" checks if the variable "CurrentInteractable" is a pointer to a valid object that's not being garbage collected. If that's true, the interface function "EndInteract" is called on "CurrentInteractable", which implements the interface, before being set to null.

7/

1 month ago 0 0 1 0

This time, I had more success reproducing the issue. It turned out to be this section of code:

if (IsValid(CurrentInteractable))
{
IWInteractable::Execute_EndInteract(CurrentInteractable, GetOwner(), false);
CurrentInteractable = nullptr;
}

6/

1 month ago 0 0 1 0

The stack trace didn't make sense so I tried re-running the same exact test. This time, I couldn't reproduce it after ten minutes. I was too tired to continue mashing on my keyboard so I did what anyone would do: create another timer to trigger the key automatically. (Thank you EnhancedInput!)

5/

1 month ago 0 0 1 0

I wondered if the key trigger was the issue so in addition to the timer, I manually pressed the key repeatedly. So now, the Actor might die either as a result of the timer or because of my key presses. After a few minutes, Visual Studio complained about a null pointer access!

4/

1 month ago 0 0 1 0
Advertisement

(For reference, we're using Unreal 5) So first, I created a timer to repeatedly spawn and destroy the Actor. I also set the console flag "gc.CollectGarbageEveryFrame 1" to force Unreal's garbage collector (GC) to run every frame. I've caught some bad bugs this way. No luck this time, though.

3/

1 month ago 1 0 1 0

In our playtest build, the game crashed when we pressed a key on the keyboard to trigger an event. What we expected was an Actor object to be destroyed, not a crash. Unfortunately, I couldn't reproduce the crash in my development version nor in the playtest build. Why should life be easy?

2/

1 month ago 1 0 1 0