Skip to content

Conversation

@UE4SS
Copy link
Collaborator

@UE4SS UE4SS commented Dec 28, 2025

Description

This PR makes NotifyOnNewObject work for unloaded classes.
Note that this PR touches performance sensitive code, that can affect asset streaming or loading times.
I haven't personally noticed any slowdowns as a result of these changes, but be on the lookout for that.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

In Palworld, with the following script:

NotifyOnNewObject("/Script/Engine.PlayerController", function(ConstructedObject)
    print(string.format("NOTIFIED: PlayerController: %X\n", ConstructedObject:GetAddress()))
end)

NotifyOnNewObject("/Game/Pal/Blueprint/UI/DamageText/WBP_PalDamageText.WBP_PalDamageText_C", function(ConstructedObject)
    -- Doesn't exist at the main menu.
    -- Is constructed when you take damage, so to test, simply take damage, for example via freezing.
    print(string.format("NOTIFIED: WBP_PalDamageText_C (Always): %X\n", ConstructedObject:GetAddress()))
end)

NotifyOnNewObject("/Game/Pal/Blueprint/UI/DamageText/WBP_PalDamageText.WBP_PalDamageText_C", function(ConstructedObject)
    print(string.format("NOTIFIED: WBP_PalDamageText_C (Once): %X\n", ConstructedObject:GetAddress()))
    return true
end)

Checklist

  • I have made corresponding changes to the documentation.
  • I have added the necessary description of this PR to the changelog, and I have followed the same format as other entries.

Screenshots

Additional context

@github-actions
Copy link
Contributor

github-actions bot commented Dec 28, 2025

CMAKE-Game__Debug__Win64 Download Logs
Build Details
Name Information
PR Commit 933924a
Merge Commit e609383
Size 37.03 MB
Last Updated Dec 28, 25, 7:24:21 PM UTC
Expires At Jan 11, 26, 7:24:17 PM UTC

CMAKE-Game__Shipping__Win64 Download Logs
Build Details
Name Information
PR Commit 933924a
Merge Commit e609383
Size 33.76 MB
Last Updated Dec 28, 25, 7:30:24 PM UTC
Expires At Jan 11, 26, 7:30:21 PM UTC

@UE4SS
Copy link
Collaborator Author

UE4SS commented Dec 28, 2025

Note that I was unable to find any classes that didn't follow the Path.ClassName naming convention.
If you know of any other naming conventions, please let know of them.

@narknon
Copy link
Collaborator

narknon commented Dec 28, 2025

Would it be appropriate to do this for functions for registration in the same PR? I guess functions are a hotter path to have to do string/fname comparisons constantly

@UE4SS
Copy link
Collaborator Author

UE4SS commented Dec 28, 2025

Would it be appropriate to do this for functions for registration in the same PR? I guess functions are a hotter path to have to do string/fname comparisons constantly

I consider it out of scope, and will just delay the PR getting merged.
When someone feels like dealing with it, another PR should be made imo.

But regardless, here are some notes on RegisterHook if we were to store names instead of immediately hooking.

We would lose the ability to determine whether the function is native or not, which means we wouldn't know how to hook it.

Native functions use UFunction::RegisterHook, which replaces the UFunction::Func pointer among other things, meaning we need a UFunction to begin with, although if it's native, would it always exist ?

While BP functions go through ProcessInternal or ProcessLocalScriptFunction.
If we can somehow determine with accuracy that it's a BP function, we don't need the function to currently exist.
Can we safely assume that it's a BP function if it starts with /Game/ ? Because the name is literally the only thing we have to go by to figure out if it's native or BP.
It shouldn't be more expensive than it already is, because we're already doing name checking for BP hooks.

@narknon
Copy link
Collaborator

narknon commented Dec 28, 2025

We would lose the ability to determine whether the function is native or not, which means we wouldn't know how to hook it.

Native functions use UFunction::RegisterHook, which replaces the UFunction::Func pointer among other things, meaning we need a UFunction to begin with, although if it's native, would it always exist ?

While BP functions go through ProcessInternal or ProcessLocalScriptFunction.

I think we can also mark the BP funcs native later on so they still go through our hook first rather than dealing with distinguishing

attempt_to_call_callback(object_class);
for (const auto comparison_class : Unreal::TSuperStructRange(object_class))
{
attempt_to_call_callback(comparison_class);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original code also followed this pattern so doesn't have to be fixed here I guess, but currently it's doing
for each super class -> for each callback. It would be less iterations to do for each callback -> check against each super class

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I haven't done any performance testing here, have you ?
I'm going by the knowledge that iterating linked lists is slower than iterating vectors, so therefore it makes a certain amount of sense to iterate the linked list just once and the vectors multiple times.
But as I said, I haven't done any performance testing for this particular use-case, and I'd expect this to not be the case with more and more callbacks, performance testing is needed to figure out which is faster at a reasonable number of callbacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants