Unreal Engine Classes Within

The title has been deliberately written to match that of Final Fantasy: The Spirits Within. With that taken care of, let me focus on the Unreal Engine part. Within is a class specifier for Unreal Engine C++ UCLASS() macro. The official documentation is like so

Within=OuterClassNameObjects of this class cannot exist outside of an instance of an OuterClassName Object. This means that creating an Object of this class requires that an instance of OuterClassName is provided as its Outer Object.

A use case is

UCLASS(Within=STWeapon)
class SUNOVATECHZOMBIEKILL_API USTWeaponState : public UObject
{
 GENERATED_UCLASS_BODY()
 ...
}

where class ASTWeapon is defined like so

UCLASS()
class SUNOVATECHZOMBIEKILL_API ASTWeapon : public UObject
{
  GENERATED_UCLASS_BODY()

public:
 /**
   * @brief Getter for the Owner of this inventory
   */
 ASunovatechZombieKillPawn* GetSTOwner() const
 {
  return STOwner;
 }
  ...
}

Now the GetSTOwner() can be accessed via following code in USTWeaponState

GetOuterASTWeapon()->GetSTOwner();

Iterators : The proper way

यदा यदा हि धर्मस्य ग्लानिर्भवति भारत

धर्मसंस्थापनार्थाय सम्भवामि युगे युगे ॥7-8, Chapter 4॥

A shalok or verse, from Geeta, that is known to many Hindus, or the gist of which gets reflected in our actions (I am a Hindu) anyway, is the representation of iterative theme, of course, coupled with a right action mentioned above. The meaning implies that Shri Krishna takes birth in an epoch, time again, whenever there is rise of evil, to establish law and order.

Unreal Engine has a way of iterating over the C++ objects which has been used in hundreds of AAA games covering a stretch of couple of decades.

The iteration is done like so

for (TActorIterator<AActor> ActorItr(testWorld); ActorItr; ++ActorItr)
{
	// print name
	KR_INFO("Iterating over actor: {0}", (*ActorItr)->GetName());
}

The TActorIterator template is defined here. The pseudo code for iteration roughly is

  1. Initialize ActorList with the appropriate UObjects (of a world or editor relevant objects for instance) of cached objects.
  2. Appropriately define (overload) the increment (++) operator with desired class filters and appropriate checks

A fruitful thing is to think what remains constant and what doesn’t in this way of iteration, which in turn defines the concept of iteration.

DELEGATES IN UNREAL ENGINE

Delegates are user defined datatypes that can be bound to C++ function(s) (or blueprint event(s)) of an object in a generic way. This means that the object need not be even declared for a particular delegate. Let me demostrate.

In Unreal Engine, a delegate is declared like so:

DECLARE_DYNAMIC_DELEGATE_TwoParams(FAnEvent, float, TimeCoordinate, float, SpaceCoordinate);

This just tells the Engine that we are declaring a user defined datatype (a delegate) which can be bound to a C++ routine with two parameters of type float. Now this delegate can be bound to any member function (with signature void (float, float)) of any object.

For demonstration purposes, consider the following code (TP_PickupComponent.h)

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPickUp, ABPToCodeDemoCharacter*, PickUpCharacter);

Furthermore, we note the blueprint usage property of the delegate (see)

UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnPickUp OnPickUp;

Finally, we observe the application of the delegate here, like so

void UTP_PickUpComponent::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// Checking if it is a First Person Character overlapping
	ABPToCodeDemoCharacter* Character = Cast<ABPToCodeDemoCharacter>(OtherActor);
	if(Character != nullptr)
	{
		// Notify that the actor is being picked up
		OnPickUp.Broadcast(Character);

		// Unregister from the Overlap Event so it is no longer triggered
		OnComponentBeginOverlap.RemoveAll(this);
	}
}

This basically translates to the meaning that when the overlap detector of the pickup detects, well, an overlap with player’s character (no pun intended), then notify all the registered events (bound to OnPickUp variable of type delegate defined above) and run the appropriate logic in their respective classes.

The blueprint equivalent of registering event by binding to OnPickUp delegate (.AddDynamic()) is like so (Bind Event On Pick Up node)

The blueprint equivalent of

OnPickUp.Broadcast(Character);

is Call On Pick Up node

A nice property of the delegates is that they can be used in appropriate arbitrary classes (mentioned above). Look at the call to Binding Delegates node, which is a function of class ABPToCodeDemoCharacter (which is not declared or defined where the delegate is declared). The function is defined like so

We can see that an event AnEvent is bound to OnPickUp in the class even though OnPickUp is not declared/defined in the class. The event is like so

Checkout the commit Delegate demonstration (literally :D) to see the logic in action.

A fun fact I saw while working in Sunovatech Pvt. Ltd is that delegate can be passed as a C++ function parameter https://forums.unrealengine.com/t/delegates-as-parameter/299175/13.

C++ Typecasts – An Assembly POV

This post may be considered a continuation of the post Addressing The Addresses or a separate read. We are basically interested in understanding how type-casts are done at assembly level, corresponding to C++ language, which may give a clue about the address arithmetic being performed leading to the offsets discussed in the post I mentioned in the beginning.

First let me demonstrate the typecasting of primitive types (I am taking some stuff from this wiki page). Consider the C++ code

int aVar = 65;
int* intPointer = &aVar;
    
char* charPointer = (char*) intPointer;

In the last line we are doing an explicit type conversion from int to char.

From assembly instruction perspective, there is no difference between int and char as far as the casting is concerned. Information about one pointer (of type int) is shared with the different pointer (of type char). Only the dereferencing bit differs like so

char b = *aP;         mov     rax, QWORD PTR [rbp-8]
                      movzx   eax, BYTE PTR [rax]
                      mov     BYTE PTR [rbp-9], al

while for int

int d = *cP;          mov     rax, QWORD PTR [rbp-24]
                      mov     eax, DWORD PTR [rax]
                      mov     DWORD PTR [rbp-28], eax

for char type of dereferencing, BYTE is read, while, for int type of dereferencing, DWORD is read.

Moving on, we now consider user defined data types, specially classes. Consider the following code

#include <iostream>
class UObjectBase
{
    int a;
    int b;
};

class UObject : public UObjectBase
{
public:
    virtual ~UObject() = default;
};

// Type your code here, or load an example.
int main()
{
    UObjectBase bar;
    UObjectBase* ptr = &bar;

    UObject* fp = (UObject*) ptr;

    std::cout << &bar << '\n';
    std::cout << fp << '\n';
}

The out put of above program may be

0x7ffe4dd2f998
0x7ffe4dd2f990 // 8 bytes offset

however on removing the virtual function (the destructor ~UObject()) may lead to the out put

0x7ffe4dd2f998
0x7ffe4dd2f998 // same address

Here, we are basically down casting from UObjectBase to UObject and that leads to the offset of 8 bytes in presence of virtual function (vtable). The assembly code instructions (generated vis godbolt) look like

    UObject* fp = (UObject*) ptr;    cmp     QWORD PTR [rbp-8], 0
                                     mov     eax, 0
                                     mov     rax, QWORD PTR [rbp-8]
                                     sub     rax, 8
                                     jmp     .L3

where .L3 is some complex set of instructions, while, in absence of the virtual function, instructions are like so

    UObject* fp = (UObject*) ptr;    mov     rax, QWORD PTR [rbp-8]
                                     mov     QWORD PTR [rbp-16], rax

In presence of the virtual function (or vtable) there is an instruction to subtract 8 bits from rax which creates the offset in addresses shown in the out put. This example was taken from stackoverflow post.

In the book “Effective C++, third edition”, item 27, Scott Myers points that

… a single object (e.g., an object of
type Derived) might have more than one address (e.g., its address
when pointed to by a Base* pointer and its address when pointed to by
a Derived* pointer). That can’t happen in C. It can’t happen in Java. It
can’t happen in C#. It does happen in C++.