This report brings to you the latest news from the lively Euloran development grounds: the GUI is shedding code and growing in usefulness, the Core adds to its services the packing and unpacking of all user actions known so far, the Data cache refuses to be led astray into unnecessary sync swamps and gets its responsibilities right, while overall all three parts arrive at a clearer way of working well together and working well apart, providing what's needed for others but minding otherwise their own business at all times. This last part of "minding their own business" took a bit of thinking over those past few days, as the newly added user actions didn't yet have a clear place of their own and it's precisely the clear and fitting definition of "own business" for each component that makes or breaks the whole: do it right and everything fits afterwards quite "efortlessly" or do it poorly and then you'll fight at every step to fit all sorts of square pegs into round holes (and rebuild the swamps, too, as a result of that sort of mindless fight).
To describe the whole thing, it's clearest (and not too long) to start from the beginning: Eulora's client has currently implemented 2 main components, namely the Core and the GUI. The responsibilities of those components have been defined initially at a high level only, as the details were not yet clear. That initial definition got gradually more detailed as I implemented more and more of the actual communication protocol but broadly speaking, the original scope is still valid: the Core is responsible for handling *all* aspects of communication with server, from network data packing and encryption to actual logic for data acquisition and storage; the GUI (as stated in its name itself) is responsible for data presentation to the user and handling all user interaction.
While the above high level view is clear enough as it is, the trouble with going into the more detailed, concrete user interaction is that its very purpose is precisely to *change* the data so carefully acquired by the Core and so neatly presented by the GUI. Add to it that any user actions in the game are more accurately defined as *attempts* to change the data, with no guaranteed outcome until and unless the server actually changes the tiniest bit of the game-world in response to them and it would seem that those user actions are indeed straddling most uncomfortably the responsibilities of both Core and GUI: get the action from the user (GUI), send the request to the server (Core) but also change the data already so that the user promptly sees the effect of their action (Core *and* GUI?). At which point, the reader familiar in the slightest with the PS coding swamps might recognise that old pattern of "all GUI elements require a connection to the server and direct knowledge of message types." Because indeed, it can easily *seem* like a ...solution of sorts, except it's of course no solution at all but simply the path of least resistance and even less reflection: it "seems" and therefore it is, why bother with anything else at all. Well, to keep this short, I'll note only that I've been toiling for quite a few years precisely cleaning up the results of others' even *more years* of following that path of least resistance.
The change to data as result of a user action is further problematic in that any change done by the client is essentially a guess: at best it gets confirmed in time by the server and therefore all is fine but at worst it gets undone (while *other* things may change too!) and that brings with it the potential further invalidation of all sorts, even of later user actions! Quite the thorny problem to handle, as any synchronizing problem ever is. So the naive approach to handling those thorns goes (again!) the route of yet another part of the PS swamps: we'll effectively mark data in the cache as "dirty" or "clean" and then go back and forth and over it all to attempt sync with the server and to handle all possible cases that are not even all that clear in advance, since they can't possibly be. For the most obvious trouble, there is no guarantee as to *which data* changes as a result of any given action so going this silly route of marking dirty data is at best a pretense to solve the problem but certainly no solving it at all. So no, naive doesn't work any better than lazy above did but it serves at least to highlight more clearly the root of the whole trouble, namely that there are in fact and inescapably TWO data caches on the client: one is indeed the explicit data cache that is held by the Core but the other is the implicit data cache that the GUI keeps in the graphical engine itself! There is no way in which the GUI can show a persistent representation of data without having therefore a cache of that data and as a result, the GUI can't shirk taking on the full responsibility for the *local* data cache where it can make whatever changes it wants, without however expecting to have any direct access to making changes on the authoritative data cache that the Core maintains.
In a nutshell, the Core aims to maintain its data cache as closely as possible to the server's view of the world, without caring at all about anything else, while the GUI aims to maintain its data cache as a reasonable guess of the world essentially: as responsive as possible to user actions and as close as possible to what the Core provides. This means that there is really no need to mark some data as dirty and some as clean in either of the two data caches: the one in Core is always and by definition as clean as the client can ever get; the one in the GUI is at best a fresh copy of that in the core and at all other times a... guess. The Core will regularly refresh its full data view by asking the server for a World Bulletin (and then following up through the world hierarchy as relevant), while the GUI will regularly refresh its full data view by asking the Core for what is in view at any given time. Whenever a user action happens, the GUI will promptly provide to the user whatever feedback it can and otherwise it will simply ask the Core to communicate that action to the server. There's no need for anything further really: whatever the result of that communication, the changes (if any!) or the undo of attempted changes (if any!) or in other words whatever effect the action has or doesn't have will be obtained anyway as part of the regular refresh of the data done by both the Core and the GUI on their respective caches.
While the above general principle works for all user actions, there are indeed some that are easier to handle than others. In particular, movement is quite apart from the rest, as it's basically a "continuous" sort of action that moreover needs to have an immediate effect shown to the user. So the GUI will have to balance here the need to synchronize the player's position with the server to avoid annoying abrupt relocations and the need to keep the user's costs low as all messages sent to the server have a cost and nobody will be all that happy to use a client that racks up huge costs just for moving about the land. Nevertheless, the great part in all of this is that it's all clearly and entirely the responsibility of the GUI and so it can be all contained in one single place and none of Core's concern. The less than great part in all of this is that the actual movement part in the current GUI is still afflicted to some degree by planeshittism1 and so it will still take some work to figure out a way to make this work, preferably cutting out or at least avoiding that remaining bit of swamp.
Other than the above specific trouble with the movement, there is still some work to be done to implement in detail everything needed for all the other, more straightforward actions. For now and mainly as a basic test, I've implemented on the client side just the simplest of them all, the "explore" so that I could confirm indeed that the whole approach works: the GUI reacts promptly to the user command and asks the Core to send the request to the server, the Core packs it correctly and sends it, the server correctly unpacks it and recognizes it as such. For that matter, I've implemented already the packing and unpacking (+tests for them) for all the actions, so the next steps will focus specifically on the higher level part of this and first in line, the clientside required bits and parts for performing those actions.
Without even counting all the parts that have been shed already in this area, there's still a psCharControl and otherwise a psLinearmove that does the actual calculations and heavy lifting but also a psMovementManager that can anyway "manage" only ONE actor at any given time and through a tangled web of back and forth calls from actor to manager and the other way around as well as from other parts to both actor and manager or to actor through the manager as well as to the manager through the actor - the usual PS "style", really ↩
Comments feed: RSS 2.0