Thoughts on Drawpile 2.2 server architecture
Drawpile 2.2 (beta) is a major reworking of the client application's internals, but the server has remained untouched. To better support the "thick server" operating mode (introduced in 2.1,) it's time to take another look at the architecture. (Thick server is what I call a server that has its own copy of the paint engine so that it can generate a snapshot of the canvas state for new users on its own, thus eliminating the need for session resets and speeding up logins. A thick server is not, however, strictly better than a thin server as it requires more resources on the server side and is tied to a specific protocol version.)
Before we can start planning the next generation architecture, we need to take a look at the current state. Here's a rough architectural diagram of the 2.1 server:
At the heart is the Session class. This represents a running Drawpile session. It contains Session History, which is the session content, past and present. The session also holds the Clients, which are the users presently logged in.
The Session class is abstract and has two concrete implementations: ThinSession and ThickSession.
The SessionHistory class is also abstract and has two implementations: one that holds the session content in memory and one that keeps it in a file. The latter is useful for very long running sessions and for running on a server with limited memory.
Now, here is the kink that needs ironing out: the ThickSession implementation does not use the SessionHistory! (Or it does but only partially in a weird way.) Instead, most of the session content is kept in the paint engine. So what bits are kept where? I for one don't remember! A neater design would be to put the paint engine into a SessionHistory implementation.
Next, the Client abstract class has a concrete implementation for Thin and Thick server modes. There is barely any code in either implementation, so they probably don't need to be two separate classes.
A better architecture might look something like this:
Less classes, no inheritance, easier to understand. The above diagram was made with the C++ implementation in mind, but the Rust version should look just about the same. One difference between Rust and C++ is that Rust doesn't have class inheritance. It does, however, have traits which are similar to interfaces in other languages. In the above diagram, SessionHistory has been changed to an interface, which should make the design compatible with the Rust way of doing things.
Another complication in the present architecture is that a Session instance keeps a list of Clients, but each Client also keeps a pointer to the Session it belongs to. In Rust, this kind of two-way reference is difficult to represent and, in any language, is more difficult to reason about. The client has a connection object through which it receives messages before passing those messages to the session it belongs to, or the login handler if not yet part of a session.
Luckily, we don't have to try replicate this architecture 1:1 in the Rust version. Just a small modification is needed:
When a client connects, an asynchronous task is spawned to handle incoming messages. Received messages are first passed to the login handler. When the login phase completes, the client is added to a session. Further messages are then passed directly to the session's message handler, removing the need for the client to have a reference to the owning session. The client instance will still contain an object representing the network connection, but this will only be used for sending, not receiving.
The new server probably won't be ready for 2.2.0 stable release, but I plan on finishing it not long after.










