Authors: Byte Game China client team – Zhou Xuan cheng, Liu Jixi
The introduction
Medium and heavy games are usually developed based on mature engines such as Unity and Unreal. Game developers will focus on the realization of the game’s own gameplay, and the technology stack is mainly concentrated in the game field. For example, login, payment, push, sharing, screen recording and other functions will be realized by mobile terminal (Android/iOS) SDK, hereinafter referred to as “Game SDK” or “GSDK”. Game SDK exposes a unified interface on the Game side, sealing functional details and platform differences within SDK. Moreover, compared with SDK, game development has obvious differences in technology stack and development language, so such development mode greatly improves the efficiency of game development. However, Game developers usually develop on PC, and the Game SDK needs the running environment of mobile platform. Take Unity as an example, the product of SDK needs to be imported, then exported to Android/iOS project, and finally packaged and verified, and installed on mobile phone to see the effect. If the function is found to be inconsistent with expectations, Need to modify the code to continue to repeat the above steps, the step will take a few minutes to a few minutes, the alignment efficiency in the development process is fatal, so we need to improve the poor alignment experience, to make the game developers in the Editor environment development in the process of the game can go to alignment SDK interface, real-time and efficient that change is what you get, Therefore, “joint tuning platform” came into being under this background, also known as “Ender platform”. The joint tuning platform gives the game development the ability of joint tuning in the real environment on PC, in which the data are real and effective, so that after successful joint tuning on PC, exported to the mobile phone environment, can directly run normally.
The overall summary
The architecture diagram is as follows:
The joint development platform is divided into game side and SDK side from top to bottom. Game side refers to game development, which is usually carried out in PC environment, while SDK side refers to mobile SDK environment, which needs to be run on Android/iOS platform. Firstly, the game developer initiates the call of a certain GSDK interface on the game side. For different game engines (Unity/Unreal), we provide Unity and Unreal plug-in support, and package it into a unified C++ interface layer at the bottom layer, which will be distributed to different terminal platforms eventually. This process is controlled by macro definition whether to use the native platform or the integrated platform. For game side access, we provide Bridge and Common access methods, which are different from each other in terms of protocol content, implementation mode and access mode. The principles of the two methods will be introduced in detail below. The interface request data will be distributed to Engine Proxy layer of the joint tuning platform, which encapsulates the core implementation of the joint tuning platform in the game side, including data protocol processing, synchronous waiting, Net Tool (network Tool of the joint tuning platform) encapsulation, Callback processing, etc. The processed data packets will be distributed to the SDK side through Net Tool. Net Tool is the cornerstone of the whole communication. Net Tool is realized through long connection and supports LAN and public network, thus completing the data transfer from PC to mobile.
Mobile SDK needs to be installed in the real APP environment and installed on the mobile phone to run. Therefore, the joint debugging platform also provides a management platform for game developers to complete the construction of the CORRESPONDING SDK version APP on the management platform. The APP side receives interface request data through Net Tool. Firstly, the message is analyzed in the Native Proxy layer, and the game side access type is determined. After passing the verification, a real interface request is constructed, and the method name and corresponding parameters of the request are obtained, which are distributed to the GSDK Bridge layer to complete the invocation of the GSDK method through reflection. If it is a synchronous method, After the method is executed, the result is encapsulated by protocol, sent to the game side through Net Tool, and finally returned to the original Engine Calls along the opposite path, thus completing the overall two-way data sending and receiving process. At the same time, Proxy layer also provides a visual interface, so that game developers can view the details of each interface in real time and conveniently, and conduct data screening, search, history view and other operations, making the whole game development experience very efficient.
Next, details and principles of each module will be introduced.
How Unity/Unreal Bridge works
Unity/Unreal Bridge solution refers to that the engine Bridge plug-in provided by SDK is connected to the engine side. The Bridge plug-in and the Native Bridge layer have agreed on the communication protocol and interaction mode. The protocol carrier is JSON, and the request corresponds to the JSON protocol. After the service JSON packet is assembled by the engine side, the Proxy layer of the joint debugging platform will forward it to the SDK side of the mobile phone through Net Tool. The Native Proxy side will obtain the complete service packet after processing, and then complete the interface request through the SDK Bridge layer.
Synchronization waiting
Because the method itself needs to call through the network, is asynchronous operation, so for part of the interface method with synchronous return value, Engine end in the call interface, must be synchronous wait, so in the Engine end need to Hold the current thread, to simulate the actual interface use. Specific call sequence diagram:
Data protocol
A common data protocol has been defined in the unified C++ Interface, but some additional fields of the network layer need to be added for the joint tuning platform. The different types of protocol fields of this scheme are listed below:
Engine Call (Engine side Call method protocol)
{" msg_id ":" ", the only number / / message "callback_id" : "", the only id / / the message callback" return_sync_id ":" ", the only id / / synchronization return "method_name" : "", / / the method name "Source ":0,// to mark the source of the message, 1-engine, 2-native "param":{},//json}Copy the code
Engine Return (Call synchronous method Return value protocol on Engine side)
{" msg_id ":" ", the only number / / message "return_sync_id" : "", the only id / / synchronization return" code ": 0, / / return code, 0 as normal" failMsg ":" ", / / the reason for the error "data" : {},}Copy the code
Engine Callback (call synchronous method Callback protocol on Engine side)
{" msg_id ":" ", the only number / / message "callback_id" : "", the only id / / the message callback" code ": 0, / / return code, 0 as normal" failMsg ":" ", / / the reason for the error "data" : {},}Copy the code
Engine Event (Receives Event protocol on the Engine side)
{" msg_id ":" ", the only number / / message "method_name" : "" / / the method name" code ": 0, / / return code, 0 as normal" failMsg ":" ", / / the reason for the error "data" : {},}Copy the code
Interface call
The interface protocol on the engine side will be distributed to the SDK Bridge layer after being processed by Net Tool and Native Proxy layer. The Bridge layer is based on SDK and is the external access layer of SDK. The Bridge layer designs different channels for different engines and isolates each other. Make the SDK available to multiple engines at the same time. Each SDK module will register the external interface of each module to the Bridge layer, and support on-demand registration. Native Proxy will select the corresponding Channel instance as required, obtain the registered method instance according to method_name and PARAM in the protocol, and complete the method invocation through the reflection mechanism.
According to the header protocol of Net Tool application layer, if it is a synchronous method, the result after method execution will be encapsulated by return protocol and sent back to the engine side through Net Tool. The engine side will release the waiting semaphore after getting the actual value to realize method return and complete a method call. For Event and Callback types, corresponding message types are added, and corresponding mapping relationship is maintained according to MSg_ID on both ends to realize Event response and Callback processing.
Principle of Common Scheme
Common scheme refers to the game without Bridge plug-in on the engine side. This scheme is based on a general premise: all the methods of calling Native on the Unity/Unreal engine side are called through C method, so to realize the universal scheme, it turns into how to dynamically call any C function containing any parameters.
Call any C function dynamically (find symbolic address)
The C function exports the symbol convention to add an underscore to the prefix, as in
void testMethod();
Copy the code
The symbol of the method is:
_testMethod
Copy the code
The address of the symbol is actually contained in the Macho file, and we just need to find the symbol name to find the symbol address.
The dynamic linker here already provides us with the ability to:
extern void * dlsym(void * __handle, const char * __symbol) __DYLDDL_DRIVERKIT_UNAVAILABLE;
Copy the code
With DLSYM we can find the corresponding address by symbol, for example:
void* functionPtr = dlsym(RTLD_DEFAULT, "testMethod");
Copy the code
For a function with no arguments and no return type, call the function pointer directly:
void (*funcPointer)() = functionPtr;
funcPointer();
Copy the code
Call to arbitrary arguments
After finding the address of the function, how to dynamically pass parameters to a function that has parameters and returns values?
In general, function calls use two basic instructions: the CALL directive and the RET directive. The CALL instruction pushes the current instruction pointer (which points to the instruction immediately after the CALL instruction) onto the stack and then executes an unconditional transfer instruction to the new code address. RET is the instruction used in conjunction with the CALL instruction and is the last instruction in most functions. The RET instruction pops up the return address (the address where the CALL instruction was pushed onto the stack earlier) and loads it into the EIP register, where execution continues.
We all know that the transfer of parameters in the function call is actually the process parameters of pressure and the stack, so the nature of the problem lies in the need for incoming parameter function, how do we stack, the parameters and the pressure after stack, how do we use these parameters in the function call, and, in turn, the pop-up when used to complete the return of these parameters.
Because in THE C language level, it is very difficult to operate the parameters of pushing and pushing directly, so we need to use assembly to help us complete.
So let’s use the LibFFI library to help us implement this complex capability, which can be divided into the following steps:
- Get the function pointer through DLSYm.
- Allocate memory space for each parameter and assemble parameter data into arrays according to FFI requirements. (Use alloca() to claim space without free())
- A cif object is assembled from the number of function arguments/parameter types/return value types to represent the function prototype. (Sort of like OC’s methodSignature)
- Allocates memory space to hold the function return value.
- Call this function by passing the CIF function prototype, function pointer, return value memory pointer, and parameter data into ffi_call.
This scheme can support most parameter type calls, but not if the input arguments are Pointers to functions.
For cases where the input parameter contains a function pointer type
In a particular game project, all callbacks are made by constructing an Action in C# and passing a pointer to that Action when calling a C method. Since all data of the joint modulation platform are called through the establishment of Bridges at the network layer, there is no such function pointer at the Native end in the actual call, that is to say, a function pointer must be dynamically constructed.
Since all function address binding symbols must be packaged in an executable when macho is packaged, it is impossible to generate a function dynamically.
Is there any other way to create a function dynamically, push it on the call, push it off the stack?
It is possible to define a generic implementation of a function in advance and use assembly code to manually collect input and return parameters at execution time. LibFFI can also be used to do this.
The summary of the process is:
- Prepare a function entity, CInterpreter.
- A cif object is assembled based on the number of function arguments/parameter types/return value types to represent this function prototype.
- Prepare a function pointer _cPtr for the call.
- Associate the function prototype _cifPtr/function entity CInterpreter/context object self/function pointer _cPtr with ffi_closure.
This method also identifies and identifies each C function created by passing in userData.
.net Tool principle
.net Tool bearing alignment platform all network (including LAN and wan) to send and receive ability, in alignment scheme, whether for synchronous and asynchronous scenario requires the ability of passive notice, so we need a long connection scheme,.net Tool is the core of the alignment platform, about the success of interface call directly.
LAN scenario
For the LAN scene, there is no need for external network participation, so the mobile APP side needs to have the ability of the Server side, to provide interface services for the PC game side, using local Socket long connection, by the PC game side to complete the Client ability access, LAN environment is very stable and efficient, In this scenario, the PC and mobile phone need to stay in the same LAN environment. The mobile phone APP will establish a long-connected Server and monitor the corresponding port. The PC, as a Client, establishes a long-connected Server based on the IP address and port number of the Server and transmits data according to the established protocol.
Wan scenario
In the wan scenario, both the PC game side and the mobile App side are clients. The Server on the public network realizes the long connection to the Server. After the long connection on the public network is established, the data on the PC and App side is transparently transmitted to achieve the purpose of data sending and receiving. The data protocol must be consistent with the LAN scenario.
Data processing process
Taking the LAN scenario as an example, the figure shows the overall data processing flow of Net Tool, which includes the following steps:
- After the interface request protocol data on the PC is sent to the Net Tool layer, data packets are assembled, the header protocol of the application layer is added, and fields CallID (unique interface IDENTIFIER), CallType (scheme type), and HasRetValue (Whether there is a return value) are added
- Next, the network layer header protocol will be added, because at the bottom layer, messages are sent and received through the binary long connection channel, which is a streaming transmission channel. At the upper layer, complete packets may be divided and combined into different packets for sending, but the receiver needs to obtain each complete packet. Therefore, the protocol in [A-b][JSON-data] format is added to the data header to identify the length and type of data packets during network transmission, thus resolving the problem of packet sticking and packet unpacking during network transmission
- The message sending adopts the producer and consumer model. The message will be added to the cache queue in a thread-safe form, and the Loop mechanism is used to continuously process the packets in the queue, and the underlying interface is called to complete the message sending
- The interface data of the game engine side is sent to the APP side through the binary long connection channel. In the Native Net Tool layer, port 9898 is continuously monitored, the received data is continuously added to the cache queue, the header protocol of the network layer is resolved, and the length and type of the complete packet are obtained. If the length of the received data packet is smaller than the total length, the system will cache the data packet and continue receiving the data packet until the length of the next data packet meets the requirement of the length of the complete data packet. The system will intercept the length of the complete data packet and distribute the data packet to the lower layer after processing
- At the same time, mechanisms such as length judgment, regular check, and message retry are used to increase the fault tolerance of message sending and receiving to prevent packet loss caused by network problems, which affects the access to SDK interfaces
conclusion
Above in this paper, the design of the alignment platform the overall train of thought and scheme principle of detail, analyzes from the engine side to side of the SDK data flow and processing of the whole link, game developers with the aid of this platform can be done in the PC side efficient mobile SDK ability of access, improve the efficiency of multiple access and access to experience, to make the game studio is more focused on the realization of the game, Currently the platform has the bytes in the internal and external two dozen game fall to the ground, all positive feedback, we will also continue to optimize and iterative alignment the usability and stability of the platform, expanding the platform function, the platform for efficiency, hope in the future can solve the problem of the efficiency of all in the process of game development, and bring new development experience for game developers.