Implementing Client/Server Tools Architecture

Introduction
In the past few weeks, I was rewriting my toy engine. During the rewrite of the tools, I want to try out the Client/Server Tools Architecture that used in Insomniac Games because this architecture has a benefit that it is crash proofing and have "free" undo/redo. My tools is built using C# instead of web-apps like Insomniac Games because there are not much resources about it on the internet as they said. I don't want to spend lots of time to solve some tricky problems in my hobby project and instead focus on graphics programming after the tools are done. Currently only material editor, model editor and asset browser are implemented.

Screen shot showing all the editors

Overview
The client/server architecture for tools is that, all the editors are clients and need to connect to the server to perform editing. All the editing states about a file are maintain in the server. The client/server communicate by sending/receiving JSON messages (which is easy and fast to parse) such as:

          {
                 "message type" : "load file",
                 " file path" : "package/default.texture"
          }

The client will send request to the server such as loading a file (all the assets are in JSON format such as mesh exported from Maya) and the server will reply with the appropriate response such as the loaded file, or whether the request operation is successful. If there is no client request, the server will do nothing, the clients will keep polling about the change of the editing state. My tools are designed both the client and server are run on the same machine and can run without internet connection.

The Server
The server is responsible for handling a set of client request such as create a new JSON file, load file, save file, change file...  The server maintains the opened JSON files that loaded/created by the clients. An undo/redo queue is associate with each opened JSON file in the server. When client send a request to modify a JSON file, the inverse change operation is computed and stored in the undo/redo queue. So if the client program crash, all the editing states including the undo/redo queue will not be lost as they are stored in the server.

The editor launcher which runs
the server in the background
The above picture showing my server program which is a simple C# program. The server is presented as an editor launcher, user can open other tools (e.g. asset browser, material editors) through this launcher. The launcher only has a few functions as shown in the UI which is to fire up the .exe of the appropriate editor, while in the background, it is a JSON file server for listening the client requests.

The Clients
All the editors are implemented as a client which need to connect to the server to perform editing. Each editor is a small application that perform only a specific task and they will keep polling changes from the server. Below shows the asset browser of the editor which is also a client program.  The asset browser is used to import assets such as mesh, texture and surface shader. Also, it can change the loaded assets package directory and the current working directory by modifying the corresponding JSON file in the server so that other editor will know about the change. Besides, assets can be drag and drop to other editors which specific the relative path of the assets inside a package and other editors can get the assets from the server.

The asset browser, importing a texture
For other tools that have a 3D viewport, such as material editor. The viewport is implemented as a unmanaged C++ .dll (which use the same code as the engine), which can be called by the C# program. During initialization of the tools, the C# program will pass the window handle of the Control (e.g. picture box) to the C++ .dll to create the D3D swap chain. Also the C++ .dll will provide several more hook up functions such as mouse up/down, viewport resize, timer update callback to the C# program. After the start up, the C++ .dll can poll changes from the server inside the update function and logically acts like a separate program which is independent of the C# program.

The 3D viewport is logically separate from the user interface
As mentioned before, the client/server architecture is crash proofing, if the client program crash, no data is lost because all the file changes are maintained in the server. But during the development of the tools, I encountered some situation that when the client program crash, the client program cannot restart. This is because the server is maintaining the file that cause the crash, so every time the client program restart, it crashes... So to avoid this problem, I added application exception handler in C# form so that every time it crashes, a dialog box would appear to ask whether to undo the last operation before the crash. This feature helps debugging a bit because every time the client crash, I can undo the changes and then the same crash can be reproduced easily by repeating the last editing step.

User is offered a last chance to undo the operation before the crash
However, not all the crash caused in the unmanaged C++ .dll can be caught by the application exception handler. Only some of the crash can be handled in C# by using [HandleProcessCorruptedStateExceptions].

Delta JSON
For the client to communicate with the server about the file changes(poll changes/make changes), delta JSON is used. The delta JSON I used is similar to the one used in Client/Server Tools Architecture. For example, we have a material file "package/default.material" like:
{
     "diffuse color" : "red",
      "specular color" : "white",
      "glossiness" : 0.5
}
To change the diffuse color to blue in the above file, a delta JSON message will be sent to the server:

{
     "message type" : "delta changes",
     "file" : "package/default.material",
     "delta changes" :
                              [
                                    {
                                             "key" : "diffuse color",
                                             "value" : "blue"
                                    }
                              ]
}
When the server receive the above message, it will change the "package/default.material" file, and at the same time, the server will compute the inverse of the delta change, i.e.
{
     "key" : "diffuse color",
     "value" : "red"
}
so that undo can be performed. Note that the delta changes is contained in the array of a JSON message since more than 1 change operation can be performed and all of the changes in a single message will be considered as an "atomic operation". The server will roll back to the previous change if the current change is invalid(such as changing a value of an array with an index greater than the length of the array).

After the above update, there may have other clients polling for the file change, the client need to tell the server what version of the file they held so that the server can send them the correct delta changes. But, instead of sending the whole JSON file to server to compute the delta changes, an 'editing step' counter is stored in the server for each opened file. This counter will be increased for each delta change (and decrease after undo). So the client only need to send their 'editing step' counter to the server and the server will know how many delta changes are needed to send back to the client. Also, this 'editing step' counter can be used to ensure that if 2 clients are making delta change requests to the server at the same time, only 1 client will success while the other would fail. However, there is one bug: there will have a chance of having the same 'editing step' but different file state if the file has perform an undo operation and the document is changed afterward. So to uniquely identify the 'editing step', a GUID is generated with each editing step. If the server check that the GUID does not match, the server cannot compute the delta changes for the client and the client may simply need to reload the whole document.

Conclusion
The client/server tool architecture approach is really a smart idea that can provide a crash proofing editor by taking the advantage of the server software would mature much earlier than the client editors. I really like this approach and glad that I give it a try to implement it. This approach gives my tools 'free' undo/redo function, multiple viewport, easier for debugging and splitting the editors into smaller client applications makes the code easier to maintain. I guess that this approach can do something interesting such as doing the rigs/animation in Maya which is live sync to the editors just like this CryEngine trailer.

References
[1] A Client/Server Tools Architecture http://www.itshouldjustworktm.com/?p=875
[2] Developing Imperfect Software: How to prepare for development failure : http://www.gdcvault.com/play/1015319/
[3] Developing Imperfect Software: The Movie http://www.itshouldjustworktm.com/?p=652
[4] New generation of @insomniacgames tools as webapp: http://www.insomniacgames.com/new-generation-of-insomniacgames-tools-as-webapp/
[5] bitsquid Our Tool Architecture: http://bitsquid.blogspot.hk/2010/04/our-tool-architecture.html
[6] Assets are exported from UDK


1 則留言: