Very impressive work!
|
Love how this is still active!
Batch updates and performance
Kris pointed out that I was handling batch updates differently; I had a batch for joining and a batch for those already in a region,
Where in osrs the batching packet is only for those in an area, those joining get everything sent as individual packets when they enter.
Which in theory is less efficient but there was a lot of extra complexity and storage used in the way I was doing it, so I opted for the simpler code.
This had the knock on effect of needing to modify FloorItems and GameObjects, systems which I wasn't overly happy with anyway so I decided to take the opportunity to rewrite those as well. FloorItems isn't that interesting just a tidy up but GameObjects came with a lot of improvements.
For context, on games first run it loads all the objects and tile data from the cache and writes it to a flat file, this means on subsequent runs it can be read faster, and while the game is running it can load map data for instances/dynamic maps without needing to hold the cache in memory (cache is discarded after startup).
Prior to recent changes, loading the full map from this flat file into memory would take on average 668.4ms and the server was using 1.1GB ram. This is much faster than using the cache directly, however still quite slow for startup. So I added a toggle which would choose to store only objects which have options, or have data in the server's objects config yaml file. Essentially only objects which are currently have content and are in use.
That cuts the stored object count from ~3.5m objects down to ~74k, and load times down to an average of 398ms and 454.68MB ram. This was the start point.
First thing to go was GameObject's as Entity's. They were heavy as each object had an event bus and a temp properties map and lots more unnecessaries, storing them instead as a Long as an inline value.
Now GameObjects were numbers they could be stored more efficiently too, I figured if you're using the objects location as the storage index you only need to store the objects id, type and rotation which all easily fit into an integer; not unlike how collision flags are stored but with the addition of the object group added to the index.
I added two implementations for storage, a simple fastutilsInt2Int
map which has a much lower memory footprint for when loading 75k objects. And a multi dimensionalArray<IntArray>
which is much faster but uses up a lot more memory, suitable for loading 3.5m objects.
The flat map file format also had some improvements, before it was storing and loading at a bit level, which is great for storage sizes but no so much for data throughput and complexity. I threw that out in favour of standard readInt/Long's.
Overall it didn't increase the size too much; the flat file is now 14.87MB instead of 14.1MB but it simplified the code and increased speed a lot.
And the final changes were making sure that the data loaded goes through transformations as infrequent and as late as possible not only in the object storage but collision storage too, along with some general refactoring and adding a checksum file so that the flat file is always correct.
Memory Usage
Total sever usage after startup has completed
Object Count Before After Memory Saved 75k 454.68MB 438.01MB 3.6% 3.5m 1.1GB 677.69MB 38.4%
Loading speed
Map loading only, average of 5 cold starts
Object Count Before Array Init Loading After (Total) Performance Gain 75k 398ms 30.6ms 139.8ms 170.4ms 57% 3.5m 668.4ms 29.6ms 202.2ms 231.8ms 65.3%
Which is really good, as you can see on the optimised version it barely peaks 500mb on startup before dropping to sub 400mb (700/500 with 3.5m objs). It's no longer a significant contributor to startup times and memory overhead although wasn't enough to breach below the 4s mark.
I'll have to do something about my yaml files if I want that.
Last edited by Greg; 06-09-2023 at 07:51 PM.
I have no idea what it all entails, but I have been watching this project with fascination for a long time. I hope that someday I can also start practicing with Java and everything related to it!
its open source feel free to help out GitHub - GregHib/void: Single player Runescape 634 server with a focus on knowledge representation and ai architecture.
Yaml parser
The majority of the games content data I store in yaml files and they've slowly been growing in size; currently 30 files at 3.2mb. Loading them all on start up was starting to become noticable; taking between 50-500ms per file.
The second issue was Jackson doesn't support yaml anchors which are a very neat and useful feature which allow you to reuse data in multiple places.
& marks an item with a name, in this case: Map<String, Any>Code:duck_grey: &duck_grey id: 6113 hitpoints: 30 race: duck examine: "Waddle waddle waddle quack." duck_swim: <<: *duck_grey id: 46 swim: true examine: "Quackers."
<< is merge key which combines the two maps (while overriding ids, examines)
* marks an alias which gets replaced with the anchor item when parsed
SnakeYAML supports anchors but it's a bit slow so I was using a mix of both libraries
So naturally I wrote my own yaml parser.
It only supports a subset of the yaml specification but has all the important parts, more importantly it's fast. This is mainly because it doesn't have any reflection usage, the downside of which is converting values to objects you have to do with a manual bit of code in a configuration class. This is an acceptable tradeoff imo.
A basic config which converts direction strings to enums during parsing:
Read speedCode:val config = object : YamlReaderConfiguration() { override fun set(map: MutableMap<String, Any>, key: String, value: Any, indent: Int, parentMap: String?) { super.set(map, key, if (key == "direction") Direction.valueOf(value as String) else value, indent, parentMap) } }
Average of 1k runs after 10 warmups loading all the config files before/after they used anchors
Parser Without anchors With anchors Json** SnakeYAML 206ms 201ms 3404ms Jackson 109ms * 130ms Mine 14ms 13ms 84ms
* Jackson doesn't support anchors
** YAML is a superset of JSON so I included the 58MB US gov electric vehicle population dataset too
The write speed is probably as equally as fast but I haven't benchmarked it as it's not as important.
7-8x faster than Jackson and over 14x faster than SnakeYAML
Switching over decreased server load times from 4.2s down to 2.9s. and has knocked off an extra 11MB ram overhead which is a nice unexpected consequence, probably due to using more efficient maps.
There's a few other places I can improve to get load times down to ~2s, not that it's really necessary but optimising stuff is fun
A few other changes; refactored a bunch of stuff to make naming more consistent (and closer to jagex), added book interfaces for quests, and jerry has added stairs for the majority of f2p areas.
Smithing, Hunt modes & Knights Sword
I've been working on Hunt Modes as Mod Ash described on twitter, it's quite possibly the or one of the last engine features that was missing. It allows for mechnics like aggression, hunter, and even the ash sweeper in varrock whom I added.
And then back to some content again; I've added smithing, smelting and superheat item spell along with bots and a number of bot bug fixes and improvements.
While Jarry has added The Knight's Sword quest
The content is adding up nicely and while there's still a number of skills left to do the only thing which really seems to be missing mechanics wise is bosses/combat scripts so I think that's what I'll focus on over the next few months
Last edited by Greg; 08-09-2023 at 04:03 PM.
« Previous Thread | Next Thread » |
Thread Information |
Users Browsing this ThreadThere are currently 1 users browsing this thread. (0 members and 1 guests) |
Tags for this Thread |