Finally back from my trip. A bit of a slow start returning to my work routine. Today I worked on static data design, outlining the static data files I think I'm going to need, and what's going to be in all of them. I also started grappling more earnestly with questions of scale--how big the world is, how far apart things are, and how far it takes to get around.
I also found a flash game online that's built on the core trading premise I'm pursuing. I thought it was rather weighed down by the bolted-on addition of a tactical RPG, as well as some truly grognardian levels of simulation detail. It was also all but impossible to figure out what to buy to make the most money, because the interface doesn't really help you--you need to actually wade through some NPC conversations to figure out what people are buying. But underneath all that, the core act of buying something and then selling it for a profit felt pretty gratifying, which made me happy.
Wednesday, August 31, 2016
Tuesday, August 30, 2016
worldbuilding
Writing from Maine. Sorry for the missing post yesterday--I did get back to work after a fashion, and managed roughly half a day. Since I've always found the summer cabin conducive to writing, I spent time working on worldbuilding. I am building out one of the six regions of the continent of Almaia, the Farahdi Peninsula. It's a little daunting to think about how many place-names I'm going to need, but I'm trying to do just a bit at a time (and the friends-and-family launch goal is to have just one region open).
I have a bunch of chores to do today, as well as travel, but I'd still like to get in another half-day.
I have a bunch of chores to do today, as well as travel, but I'd still like to get in another half-day.
Tuesday, August 23, 2016
integrating unity project into tree
More than a month ago now, I was working on a simple Unity project called MapTest. Basically it just rendered a map texture and then placed some "town" icons down on it via script. The organization of the project was very prototype-y (i.e., there was no organization). Now has come the time to integrate that humble beginning into my main project branch. I spent most of the day cleaning up this project and getting it working from my build shell. I also did some planning for the next several steps I need to do hook up the unity project (now called clientviz) to the backend I've already written.
GameFrame is fully functional as of this morning. I was able to do a full integration test successfully. Because the client and server were running in the same process, I could do a whole series of PlayerState operations and then compare the client and server PlayerStates to confirm their equality. This was a little tricky because of comparing a CPlayerState and SPlayerState (server and client specializations), but it turns out the Kellerman comparer has the right config options to support this.
I will be traveling tomorrow but hope to get a 1/2 day in. Then I will be off until Monday 29th.
GameFrame is fully functional as of this morning. I was able to do a full integration test successfully. Because the client and server were running in the same process, I could do a whole series of PlayerState operations and then compare the client and server PlayerStates to confirm their equality. This was a little tricky because of comparing a CPlayerState and SPlayerState (server and client specializations), but it turns out the Kellerman comparer has the right config options to support this.
I will be traveling tomorrow but hope to get a 1/2 day in. Then I will be off until Monday 29th.
Monday, August 22, 2016
GameFrame
Worked on automated full-lifecycle tests for the game. Someday GameFrame will be used to do client-server integration testing, but right now it's running a local server, and exercising all the client->server events that currently exist. Still a couple problems to work out, but close. Once this step is done it's back to Unity.
Sunday, August 21, 2016
CVClient, GameFrame
Well, it's actually called CaravaneerClient now, not CVClient. Turns out that naming a class CVClient inside a namespace CVClient is a bad idea, even though the language lets you do it. In any case, it's stubbed out to a fair degree of maturity. I'm now starting to rough out GameFrame, the command-line test tool that does integration testing from inside the Mono Solution. The hope is to have the server and client parts of the game all talking to each other, in a way suitable for batch-mode testing, all without having to touch Unity.
Friday, August 19, 2016
CPlayerState
built out the second PlayerState request object: AdminGrantRequest. Added CPlayerState and started work on the client side of the transaction pipeline.
I'm thinking about how best to build a request/response pattern on top of the networking layer I wrote. That layer is entirely object based (just streams of objects in each direction). I think I'd like to keep it that dumb, and have ClientRequest /ServerResponse objects that contain the necessary state to provide callbacks for specific requests on the client.
Thursday, August 18, 2016
SPlayerState, mossbin
Today I straightened out an issue with PlayerState and SPlayerState that I was unsatisfied with. As written, SPlayerState extended PlayerState compositionally. It did this because of serialization. One of my original design choices for mossbin was that serialization was heritable. that is, if you had:
[WritableClass]
PlayerState {}
SPlayerState : PlayerState {}
SPlayerState would inherit the WritableClass attribute and implicitly become a serializable class (with the exact same fields as its parent).
But now imagine on the server you have this:
[WritableClass]
LoginResponse
{
[WritableField]
PlayerState m_state
}
If you fill out m_state with an SPlayerState, the server will write out an SPlayerState--even though that type is defined in the server dll! Obviously the client won't know what to do with this thing. For this reason, SPlayerState contained a PlayerState via composition. However, as I'm about ready to build out the client side, including creating a CPlayerState, I began to feel the limitations of this pattern. Actual inheritance would be tidier for my purposes.
So I changed my tune. Now [WritableClass] is NOT an inheritable attribute. In addition, if mossbin encounters a [WritableField] that is a base-type reference pointing to a non-serializable child, it will automatically walk up the inheritance tree, looking for a base type to serialize as. So you CAN assign SPlayerState to the PlayerState reference, and have it serialize as a PlayerState that the client can understand.
In a way, this is its own sharp edge (I can imagine someone--maybe future me--being bamboozled by the fact that I had a class with m_parent->child on one side of a serialization, and got back m_parent->parent on the other side). But it's super-convenient for this server-client relationship, where specializations on both sides want to communicate by their common base class.
This actually created a new missing feature I had to document. Say you have:
[WritableField]
Parent m_parent; //(points to type Child after instantiation)
You might want to serialize this as a Parent in one situation (say, sending something down to the client, but actually serialize it as the Child that it is in another (say, the server saving its own specialized state into the database). This will entail a new property on WritableField, and some extra logic in mossbin.
I'm still not sure writing my own serialization layer was a good idea, but it sure is fun being able to tune things at this level of detail.
[WritableClass]
PlayerState {}
SPlayerState : PlayerState {}
SPlayerState would inherit the WritableClass attribute and implicitly become a serializable class (with the exact same fields as its parent).
But now imagine on the server you have this:
[WritableClass]
LoginResponse
{
[WritableField]
PlayerState m_state
}
If you fill out m_state with an SPlayerState, the server will write out an SPlayerState--even though that type is defined in the server dll! Obviously the client won't know what to do with this thing. For this reason, SPlayerState contained a PlayerState via composition. However, as I'm about ready to build out the client side, including creating a CPlayerState, I began to feel the limitations of this pattern. Actual inheritance would be tidier for my purposes.
So I changed my tune. Now [WritableClass] is NOT an inheritable attribute. In addition, if mossbin encounters a [WritableField] that is a base-type reference pointing to a non-serializable child, it will automatically walk up the inheritance tree, looking for a base type to serialize as. So you CAN assign SPlayerState to the PlayerState reference, and have it serialize as a PlayerState that the client can understand.
In a way, this is its own sharp edge (I can imagine someone--maybe future me--being bamboozled by the fact that I had a class with m_parent->child on one side of a serialization, and got back m_parent->parent on the other side). But it's super-convenient for this server-client relationship, where specializations on both sides want to communicate by their common base class.
This actually created a new missing feature I had to document. Say you have:
[WritableField]
Parent m_parent; //(points to type Child after instantiation)
You might want to serialize this as a Parent in one situation (say, sending something down to the client, but actually serialize it as the Child that it is in another (say, the server saving its own specialized state into the database). This will entail a new property on WritableField, and some extra logic in mossbin.
I'm still not sure writing my own serialization layer was a good idea, but it sure is fun being able to tune things at this level of detail.
Wednesday, August 17, 2016
TownSnapshot
The big thing I did today was to get town snapshots working. Basically the thing I talked about yesterday. There's now an EnterTownRequest object, and when it goes through, the server caches the state of the town's market off on the PlayerSession (this is all a bit of a puppet show, because there's no time-variability in prices yet).
I also stubbed out the cvclient dll. For a long time I wasn't even sure I'd need one, since the interactions on the client with game state didn't seem too rich (rather, the unique, clienty bits). But I eventually talked myself into it. I think it might be a good design goal of the Unity visualization layer wasn't even allowed access to the shared-dll namespace (even though of course it's linking the shared dll). That would be some good concept-isolation between the game and the visualizer, if it could be done.
I also stubbed out the cvclient dll. For a long time I wasn't even sure I'd need one, since the interactions on the client with game state didn't seem too rich (rather, the unique, clienty bits). But I eventually talked myself into it. I think it might be a good design goal of the Unity visualization layer wasn't even allowed access to the shared-dll namespace (even though of course it's linking the shared dll). That would be some good concept-isolation between the game and the visualizer, if it could be done.
Tuesday, August 16, 2016
EconomicSim, CitiesData
Built out placeholders for EconomicSim and CitiesData. Beefed up WorldState. Added another worker thread for WorldState to run in (CVServer now manages two; one for world state and the other for the session manager handling client connections).
I realized I hadn't really thought a part of my design all the way through. When you bought or sold a good, the idea was you'd just consult static data to find out how much the good was (which would be the same between server and client, because that state is all server authoritative). But my design for good prices is time sensitive (and potentially sensitive to other variables). The client needs a snapshot of state from the server that it will know is good for a certain amount of time, and then that needs to get threaded through to all PlayerState-modifying requests. To do tomorrow.
I'm really hoping to return to Unity soon--it would be nice to start hooking up all this backend to something graphical.
I realized I hadn't really thought a part of my design all the way through. When you bought or sold a good, the idea was you'd just consult static data to find out how much the good was (which would be the same between server and client, because that state is all server authoritative). But my design for good prices is time sensitive (and potentially sensitive to other variables). The client needs a snapshot of state from the server that it will know is good for a certain amount of time, and then that needs to get threaded through to all PlayerState-modifying requests. To do tomorrow.
I'm really hoping to return to Unity soon--it would be nice to start hooking up all this backend to something graphical.
Monday, August 15, 2016
PriceList, EnterTownRequest, EconomicSim
For the prototype, I need the server to return a PriceList of buy/sell data when reaching a town. The prices of goods are governed by the EconomicSim (a class that doesn't actually exist yet)--itself a part of WorldState. The WorldState simulation is intended to run on its own thread and expose two interfaces: a read-interface that is thread-safe, and a write-interface that is basically just a ConcurrentQueue for receiving update requests.
I've started thinking about how to do the thread-safe read interface. When I first started thinking about this problem, I was imagining some kind of double-buffering solution: two copies of WorldState, one used by the readers, and one by the WorldState simulation: after every WorldState useTime, it would swap the two with an interlocked operation.
That's not quite adequate, though. If the WorldState usetime runs again while receiver threads are still using WorldState from the last tick, you could get an unsynchronized conflict (the read-only version has now become the writable version, and is being scribbled to). I'd need to some explicit locking to prevent this, which defeats the purpose. Maybe I should just use a ReaderWriterLock and design WorldState's update pattern in such a way that the updates are very brief in duration (e.g., doing all the complicated reasoning outside the lock--knowing that our thread is the only thing that can modify the state anyway).
Saturday, August 13, 2016
CVServer, SessionManager, staticdata stubs, LocalizedString, PostDeserialize method
I stayed up later than I meant to. Almost got a workday in despite starting around 7. Lots of pretty easy stuff. I stubbed out the main server class and the SessionManager class, and wrote the logic for kicking off and joining the SessionManager's worker thread. Then I started stubbing out static data. In doing so I realized I needed to stub out localized strings. In doing that I realized I finally needed to implement PostDeserialize in my serialization layer (the idea is that after an object unpacks, the serialization logic will call a method tagged with the 'PostDeserialize' attribute to let it do final setup; LocalizedString will use this to convert from the serialized string hash Id to the actual localized string literal by referencing the localized string pool).
I finished PostDeserialize (with unit test). The rest is mostly stub. I'm very deliberately resisting the temptation to actually build out the resource model since that was explicitly a non-goal for the prototype. So all the static data is going to be populated with some hard coded values.
Friday, August 12, 2016
PlayerSession, static data
Pretty simple today. I finally worked out all my remaining failing unit tests and got a full lifecycle of the PlayerSession working. It turns out the serialization bug I thought I understood was a little more interesting. verbum sap: if you're using Type.GetField, you won't get back private fields from parent types even with the BindingFlags.NonPublic attribute set! That doesn't make a lot of sense to me; they're still private fields in the type, and you can see the other private fields. Whatever--easy enough to fix once understood.
Now I'm on to blocking out the pattern for static data, although I think it will be a while before there actually is any (i.e. files, instead of things hardcoded in classes).
Now I'm on to blocking out the pattern for static data, although I think it will be a while before there actually is any (i.e. files, instead of things hardcoded in classes).
Thursday, August 11, 2016
more PlayerSession, unit tests
Not much to report today; was traveling back from Maine into the hot sticky weather of Boston. I worked a bit more on PlayerSession and found a new bug in the serialization layer.
Wednesday, August 10, 2016
PlayerSession, unit tests
Sorry for the lack of post yesterday; I took it as a vacation day and went on a beautiful hike, and to a concert in the evening.
Today I continued work on PlayerSession, the core class for managing player's connection lifetime. I've come up with some pretty intricate unit tests for it, including a full life cycle (connect, do a PlayerState transaction, and shutdown), and several failure cases. I discovered a problem in NetConnection; I had done some very server-centric error handling in receiver thread. To do that error handling I had had to mix in a more specialized namespace that I had intended to keep separate. Turns out that was wrong anyway--the logic I wrote wouldn't make any sense on the client. One of those cases where, if I had done it properly the first time, the system would have "just worked".
Up to 105 unit tests now.
Monday, August 8, 2016
PlayerSession, SPlayerState, unit tests
I got significantly closer to completing all code necessary for the full client lifecycle today. That will encompass logging in, making a simple transaction (buying or selling some stuff) and then logging back out again. I've added the server dll and its first class, SPlayerState, a compositional child of PlayerState. It contains the server's message handling logic for PlayerState requests. There will be something very similar on the child where, if you want to, say, buy something on the PlayerState, you call the base class (doing the actual buying locally), and then do the logic of creating a TransactGoodRequest object and sending it to the server.
I've also been building out the logic for rolling back from a ValidationException. Most exceptions cause a client disco, but ValidationExceptions (which are thrown by game code when an update to the PlayerState doesn't pass muster) send back a whole copy of the "good" PlayerState. My hope is to hook this up so that it resets the user to a known good state in a much less traumatic way than a disco (maybe showing a wheel rolling backward or something, to fit the caravan theme).
I'm writing this on my laptop from the family place in Maine; I took a 2 hour lunch to go windsurfing. Liking the self-employment so far.
I've also been building out the logic for rolling back from a ValidationException. Most exceptions cause a client disco, but ValidationExceptions (which are thrown by game code when an update to the PlayerState doesn't pass muster) send back a whole copy of the "good" PlayerState. My hope is to hook this up so that it resets the user to a known good state in a much less traumatic way than a disco (maybe showing a wheel rolling backward or something, to fit the caravan theme).
I'm writing this on my laptop from the family place in Maine; I took a 2 hour lunch to go windsurfing. Liking the self-employment so far.
Thursday, August 4, 2016
cork work; message handlers
Nothing to exciting today. Blocked out the server dll and implemented the PlayerSession class. Threaded in the message handlers that move message objects out of the NetConnection and then to the PlayerState. By design, all this happens in the receiver thread, so replying to a request doesn't entail crossing any thread boundary.
Wednesday, August 3, 2016
back to work; core request/response classes
Finally completed my house move and resumed regular work. Today I continued work on the underlying ClientRequest / ServerResponse, message handling, and exception handling pattern. In doing so I think I've bumped up against a bug in my underlying serialization layer, which is exciting because I have a lot of unit tests in there.
Subscribe to:
Posts (Atom)