Shared Logic
Daniil Korostelev
(Forge of Empires)
Problems to solve:
Code duplication
- Backend, frontend(s!), admin tool, other tools
- A lot of effort to implement
- Lots of code to maintain
- Much space for mistakes
Problems to solve:
Unclean code separation
- Game logic intertwined with:
- Database
- User Interface
- Networking
- etc.
Problems to solve:
Complicated data layout
- No "player state" structure
- Hard to understand
- Hard to synchronize
Problems to solve:
Not fun to test
- A lot to setup
- Duplicated tests
Good game code
- Domain-driven
- Self-contained
- Reusable
- No boilerplate
Good game code:
Example (logic)
// data structure
class Player {
var name:String;
}
// logic entry point
class Commands {
// action
function changeName(newName) {
validateName(newName); // no swear words!
data.player.name = newName; // state change
}
}
Good game code:
Example (front-end)
// listen to changes
// (could also be "reactive" property or something)
logic.data.player.onNameChanged(
newName -> playerNameLabel.text = newName
);
// handle UI
function onNameEntered(input) {
// execute action
logic.commands.changeName(input)
}
Good game code:
Example (back-end)
function processCommand(name, args, checksum) {
// do the preparations
loadPlayerData();
startTransaction();
// call logic
var changes = logic.execute(name, args);
// consistency check
if (changes.checksum != checksum)
throw "invalid checksum!";
// save to database
commitChanges(changes);
}
Implementation:
goals
- only write code that matters
- one declaration per structure
- one declaration per action
- easy to reason about and change
Implementation:
the trick
Code generation for all the things!
- change-tracking setters
- reactive read-only data views
- command dispatcher API
- static and player data validation
Implementation:
tech
- Powerful statically typed language
- Compiler API or procedural macros
- Still simple enough to iterate
- Haxe \o/
- ...or your fav tech (to some extent)
Typical issues
- Floating point errors
- Run-time platform differences
- Multi-player, third-party API, server pushes
- Cheating
Cheating
- Cheaters are real!
- Shared logic helps
- Shared logic hurts
Preventing cheating
- Minimize command arguments
- build(id:"house", x:1, y:2)
- Check that commands are in order
- Sync time between client and server
- Be careful with random seeds
Random seed exploits
- Global random seed is handy
- ...but exploitable:
- block connection
- check the result
- don't like the result, reload game
- change the seed by some other action
- try again
- Two ways to fix:
- Local seeds (can see, cannot affect)
- Server-only random actions
Server commands
- (Mostly) like normal commands
- Executed only on the back-end
- Changes are returned to the front-end
- Have access to additional server APIs
- Useful for: tournaments, matchmaking, payment, etc.
Exchangeables
- "Messages" with id and payload
- Sent by both game logic and admin tools
- Handled by the game logic
- Useful for: news, gifts, mail, async player interactions
Queries
- Expose calculations
- Must not modify state
- Mostly for front-end
(Almost) true multiplayer:
tricky
- Shared state between players
- Potentially outdated state
- Cannot be used for normal game logic
- Only safe to use for display and UI confirmations
(Almost) true multiplayer:
server commands
- Server commands API
- Shared state should be locked
- Changes dispatched to all participants
- Commands should be more tolerant to invalid input
- ignore the duplicate action if possible
- return meaningful error if not
Back-end implementation
- Storing JSON in Postgres tables worked for us
- Player state is stored as snapshot + changes
- Changes applied to the snapshot periodically
- Some data is mapped to DB indexed columns
State migrations
- needs lower-level data access
- easy with JSON
- possible anyway
- can be lazy (only migrate when requested)
- automatic detection if migration is needed
- compare schemas
- detect references to static data
Further exploration
- Multiple data "roots"
- Multiple logic modules
- "Real" multi-player framework?
Thank you!
Shared Logic
By Dan Korostelev
Shared Logic
- 1,070