AActor* UWorld::SpawnActor( UClass* Class, FVector const* Location, FRotator const* Rotation, const FActorSpawnParameters& SpawnParameters ) { ... AActor* const Actor = NewObject<AActor>(LevelToSpawnIn, Class, NewActorName, ActorFlags, Template, false/*bCopyTransientsFromClassDefaults*/, nullptr/*InInstanceGraph*/, ExternalPackage); // Add this newly spawned actor to the network actor list. Do this after PostSpawnInitialize so that actor has "finished" spawning. AddNetworkActor( Actor ); return Actor; }
voidUPendingNetGame::BeginHandshake() { // Kick off the connection handshake UNetConnection* ServerConn = NetDriver->ServerConnection; if (ServerConn->Handler.IsValid()) { ServerConn->Handler->BeginHandshaking( FPacketHandlerHandshakeComplete::CreateUObject(this, &UPendingNetGame::SendInitialJoin)); } else { SendInitialJoin(); } }
在继续分析之前,先来看官方的注释,描述了登录的流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Most of the work for handling these control messages are done either in UWorld::NotifyControlMessage, and UPendingNetGame::NotifyControlMessage. Briefly, the flow looks like this:
Client's UPendingNetGame::NotifyControlMessage receives NMT_Challenge, and sends back data in NMT_Login. Server's UWorld::NotifyControlMessage receives NMT_Login, verifies challenge data, and then calls AGameModeBase::PreLogin. If PreLogin doesn't report any errors, Server calls UWorld::WelcomePlayer, which call AGameModeBase::GameWelcomePlayer, and send NMT_Welcome with map information. Client's UPendingNetGame::NotifyControlMessage receives NMT_Welcome, reads the map info(so it can start loading later), and sends an NMT_NetSpeed message with the configured Net Speed of the client. Server's UWorld::NotifyControlMessage receives NMT_NetSpeed, and adjusts the connections Net Speed appropriately.
template <SIZE_T NumBits, typename SequenceType> classTSequenceNumber { static_assert(TIsSigned<SequenceType>::Value == false, "The base type for sequence numbers must be unsigned");
public: using SequenceT = SequenceType; using DifferenceT = int32;
#define G_NEW 0 /* created in current cycle */ #define G_SURVIVAL 1 /* created in previous cycle */ #define G_OLD0 2 /* marked old by frw. barrier in this cycle */ #define G_OLD1 3 /* first full cycle as old */ #define G_OLD 4 /* really old object (not to be visited) */ #define G_TOUCHED1 5 /* old object touched this cycle */ #define G_TOUCHED2 6 /* old object touched in previous cycle */ #define AGEBITS 7 /* all age bits (111) */
TigerShrimp 内部有个简单的 Interpreter,用以直接执行 bytecode,执行每一条 Instruction时,会记录当前的 pc (二元组,记录函数索引和指令索引,不然指令索引可能重复),是否为热路径,若为热路径,则会执行 record 流程,记录每一条执行的指令。(通常记录循环,循环有回边,记录执行次数,执行次数大于一阈值,则认为是热路径)。
在上面中,我们手动删除了 a 和 b ,理应进行释放,但由于 a 和 b 互相构成了循环引用,导致其引用计数总是不为0,进而造成内存泄漏,而 CPython 对其解决方法也极其简单,就是将所有可能造成循环引用的对象,构成一个双向链表进行扫描,从 root object 出发进行扫描 - 清除,无法到达的对象就是可释放的对象,普通的对象直接采用引用计数去释放,简单快捷。
怎么去验证以上结论呢?我们可以用反证法,当 del a 和 del b 后,再调用 gc.collect() 查看其是否能被回收到,如果能回收到,说明在此时引用计数已经失效。