I started writing another blog post last week about a big overview of my thesis project. However it fell short in meaningful content so I didn’t share it. However it is kinda too long for me to not share anything so let me give you a run down of what I did last week.
To be honest, I lost a lot of momentum coming to Malta. Which is exactly opposite of what I wanted to happen. The biggest problem is the internet in the dorms. It is slowing me down immensely. Add course assignments to that and hardly find myself working on the thesis.
I kicked my own but to restart coding. However after leaving code for a while I found myself out of the flow. Instead of moving forward with the code, I started questioning my previous design choices. Which is good of course. I believe having a fresh look helped me identify some issues with the design that ultimately made development harder.
The main point of the FlatStates was to have an unification capable world state representation which synchronizes seamlessly in real-time. In order to achieve this seamless synchronization I designed system to use actual gameobjects rather than primitive values as Axiom terms. My initial idea was that using actual objects would reduce the need to reflect world state changes to axioms. But in reality it created more problems.
First of all in order to make sure the system works properly, all axiom terms had to implement a Bindable interface. Which introduced a whole lot of unnecessary boilerplate classes. At first having more classes seemed like a nice way to make sure everything stayed structured and type safe. For instance when I create IsResource(Carriable x) as axiom I would make sure the reference given as axiom term would implement Carriable interface. But it also meant that I had to create all these Bindable interfaces which are usually empty. Their whole purpose was to mark objects for FlatStates.
Second issue is the axiom itself. When I first started I wanted to make Axioms structs for performance. Axioms are designed to represent a constantly changing world state. As such they are created, passed on, stored and destroyed all the time. Creating new typed axiom objects like IsResource meant putting a lot of pressure on garbage collection. However in c# when you cast struct to an interface it gets boxed. So having multiple struct types implementing an common axiom interface would basically remove benefits of having structs in the first place.
I ended up having Axioms as classes. So everything is garbage collected and there is a huge overhead. I could use pooling of course, but as number of axiom types increase so did the number of pools to manage. Ultimately having typed axioms meant to do more work with having less benefits.
Last week I ended up ditching the typed implementation in favor of having generic axiom class. I still haven’t completed the transition yet so the changes are not tested. But if my previous unit tests will light up green as expected I will be working with this generic axiom which comes with couple of advantages.
Axioms are structs again since there is no longer an interface to cast. I don’t need to create boilerplate classes, now I just say new Axiom(“IsResource”, “referenceId”); . Currently I am only working with string references and plan on interning all strings used in the process to make sure there is less overhead. In future I might ditch the strings references in favor of GUID objects which takes less space in memory. Also if i want to use pooling for Axioms, I will only manage one pool.
For type safety I decided to change my direction from typed axioms to static factory method. So instead of creating an axiom directly, I could use IsResource.Create(Carriable x) to make sure the system is type safe. Factory method will simply turn Carriable object to a string reference and create a generic axiom under the hood. Of course this is a boilerplate code in itself. But it is not a necessity only a better practice. So I can test new ideas quickly without the boilerplate if necessary.