[This post is for my own notes]

Difference in Normal C++ and UE4 C++

CDO v.s. Constructor

Basically the constructor of a C++ UObject is called in the following cases:

  • Engine Start: construction of CDO, in FEngineLoop::PreInit
  • BP Loading: also CDO, in UClass::Serialize (need confirm)
  • Object Loading(Spawn): normal constructor

To determine the current mode, use:

if (HasAnyFlags(RF_ClassDefaultObject)) {
    // Do CDO
} else {
    // Do normal init
}

So CDO is just for initializing of the UPROPERTY fields? Need to consider in design.

This is possibly the reason why the current version has so many bugs :)

BTW, for normal objects, just newing it is sufficient. So CDO only applies to UObjects.

GC

Big headache for me. UE4 by default garbage-collect everything that is too far away. For example, if you have a physics-enabled object that does not have collision with the ground, it will fall down infinitely, and then GC'ed as out-of-range. You may think this is not important, right? However think that when only the root component is set like that and it falls down too much-all other components behaves normally and all of a sudden your actor disappears entirely. Such situations are like nightmare for debugging-they are actually "normal" behaviors of the engine, thus leave no logs at all.

Another problem for GC is that it relies on references. And references rely on Unreal's Object System. If an object is not declared as UObject, it will not be considered in GC, which will result in memory leaks. Furthermore, if the non-UObject object refer to a GC'ed UObject, there will be a dangling pointer.

It is noteworthy that, Unreal Objects are also C++ objects. One may think, if we only use smart pointers in UObject for unmanaged objects and not refer to UObjects in unmanaged C++ code, the problem can be avoided in some cases.

However, consider the following situation: we have an UObject that needs to do some heavy lifting using a third-party library asynchronously. To do that, we call a function with a callback function as argument. Before the work is done, the UObject gets released by GC. Now the callback function becomes a dangling pointer. This example explains why it is impossible to avoid this problem in modern, async-heavy applications.

Interoperation Patterns

An RPC-like solution is always nice to have. For example, the ongoing effort on integrating Cap'n Proto will enabled cross-thread and cross-process communication both in Unreal and between other components.

Cap'n Proto is nice in that it is message loop and Promise based. In this way exceptions can be handled without using exceptions. It also supports -fno-rtti. However the internals of Cap'n Proto is not well documented due to its relatively small user base. Still needs investigation.

ROS-based solutions can directly communicate with ROS without a bridge. However, they are usually deeply plagued by Boost, RTTI and other anti-patterns. The only standalone ROS library is cROS, which is limited in function and does not have a nice API.

[TODO: figure out the final architecture and start rebuilding Pavilion.]