November 29 2021
In this post, I’ll teach you how to protect your most sensitive code from the classic “sloppy programming” by using the KISS Transaction Pattern for Unity.
With traditional programming, it’s quite easy to screw up your most sensitive data, which turns into frustration/regrets or worse, getting canned from work.
I’m talking about the fragility of executing code like this:
_player.SetLevel(_player.Level + 1);
_player.SetName(userInput)
_player.AddItem(loot[0])
Honestly, it is not so much about the input we pass (that’s another issue)…
It’s more about the fact that anyone can just call these functions anytime, anywhere, for whatever reason and mess your data up.
But we need these setters! How could we live without them?
Indeed, you need setters; how’re you going to do a level up otherwise?
But my point is that you don’t need write-access to sensitive data 95% of the time. And guess what, this is sensitive data you are ought to protect.
Messing with sensitive data just once is enough to ruin someone’s (virtual?) life. Especially if we’re talking about multiplayer games.
I guess no one other than hackers likes corrupted savegames.
At least not me. That’s why I wrote this blog post ;)
Let’s get into the meat of the post: today I’m going to help you increase the DEF stat of your sensitive data by applying the KISS Transaction Pattern.
Here’s our primary quest for today:
We are going to forbid write-access to your most sensitive-data… except to the 1% of your codebase that truly needs it.
So 99% of your code will only have “getter” access. Because last time I checked it’s hard to mess anything up if you only got read access, right?
Only carefully crafted processes will be allowed to modify your sensitive data. The concept of whitelisting, that is.
Let’s assume Jeff is playing our “never-finished MMO” and wants to change his name to Jefferson. Just because some sort of virtual trauma.
Here’s the question for you: \wWhich code should ever be allowed to call SetName?
Here are my two cases:
So yeah, we want ALL our codebase to only have read-access to the name… except for these two cases.
Before I show you the solution, here’s more data you really want to protect:
You see the common denominator?
We decide ahead of time WHEN exactly we want to allow these changes.
And for this, the transaction pattern will kindly help us build this whitelist.
Let’s see how.
Aiai… too many approaches to the transaction pattern.
And I don’t like most of them. Again, they’re often overengineered and complexity is NOT the solution to your problems.
Let me say that again out loud: complexity only brings you fresher problems.
Simple in ”KISS” means:
KISS is the acronym I stand for: Keep Things Simple(,) Stupid.
As with the other KISS Patterns, let’s build a simple solution, shall we?
These are the key sentences you must burn into your mind just like OLED-TVs burn-in reveal who watched too much p***nhub:
The KISS Transaction Pattern separates read-access (in the interface) from write-access (on its specific class). Your codebase only gets access to your read-only interface, which allows you to execute carefully crafted transactions that have write-access.
Got it?
Probably not. I also prefer examples over definitions, so let’s give this a good kick.
Let’s assume we want to protect the Name property of your player. This is how the player would look like:
public interface IPlayerProfile
{
string Name { get; }
void ExecuteTransaction(PlayerTransaction transaction);
}
public class PlayerProfile : IPlayerProfile
{
public string Name { get; set; }
public void ExecuteTransaction(PlayerTransaction transaction)
{
transaction.Execute(this);
}
}
Saw that miracle?
And now the trick is that the interface allows you to execute transactions that will eventually get write-access. Like this:
public abstract class PlayerTransaction
{
public abstract void Execute(PlayerProfile playerProfile);
}
public class AdminChangeNameTransaction : PlayerTransaction
{
private string _newName;
public AdminChangeNameTransaction(string newName)
{
_newName = newName;
}
public override void Execute(PlayerProfile playerProfile)
{
playerProfile.Name = _newName;
}
}
Key facts to remember:
Finally, your game admins can now use the transaction to safely change the player name like this:
[Inject] IPlayerProfile _playerProfile; // Note this is the interface
void OnAdminChangeNameButtonPressed()
{
_playerProfile.ExecuteTransaction(new AdminChangeNameTransaction(nameInput.text));
}
Juicy!
(Noticed how well this plays with the KISS Dependency Injection Pattern?)
Now you can go ahead and:
Some developers don’t understand the benefits of an approach like this. So here are some on top of my head:
However!!
Not everything is cute rainbows & butterflies. There’s also a sucky dark thing about this transaction pattern…
The KISS Transaction Pattern for Unity works wonders because it’s simple and makes your programming style safer. Period.
But there’s a single warning I must give you about this specific implementation: it is dangerous for your project performance.
Why?
Because these example transactions allocate memory on the heap and fragment your memory by default.
Here, see for yourself.
You might think you can fix that by converting them into a struct, but no, that will only make things worse.
The result is clear: slower framerate and occasional freezes that your players won’t particularly enjoy.
It is totally ok that you use this sample implementation during your prototyping phase. But if you’re working on a production project that you eventually want to ship, you should use the production-ready, high-performance KISS Transaction Pattern.
To gain access to a detailed lesson on the production-ready KISS Transaction Pattern with ZERO allocations, join the Performance Taskforce & unleash the beast. You’ll find all the content in module 2021.09 after you join.
Ruben (The Gamedev Guru)